I have a class RegisterType for my form:
<?php
class RegisterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('price');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
The Register Entity has a relation with an Entity called Product
I would like to add this condition to the buildForm function:
<?php
if ($product->getTitle() == 'SuperProduct') {
$builder->add('amount', 'money', [
'required' => FALSE,
]);
}
?>
If the product title has the value 'SuperProduct' then I add a field in the the form.
But I have no idea about the syntax I have to use to call another Entity value in a FormType.
Thanks in advance for your help.
Ah good you've implemented your FormType as an independent class rather than in a controller, so what I'd do is pass in your product entity as an option to a private variable within your FormType class. I'm not sure what class your product is or the name space it resides in, so you'll have to fill that in.
I've also written this in the expectation that you could have more than one option to pass through in the future, so it'll be useful to other FormType classes you write. Each variable you define in the class scope can be set when you create an instance of the FormType class. Be sure to initialise these variables as false instead of null, or the code in the constructor function won't see them.
<?php
class RegisterType extends AbstractType
{
private $product = false;
public function __construct(array $options = [])
{
foreach ($options as $name => $value) {
if (isset($this->$name)) {
$this->$name => $value;
}
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price')
;
if ($this->product && is_a("\Namespace\To\Your\Entity\Called\Product", $this->product)) {
if ($this->product->getTitle() == 'SuperProduct') {
$builder
->add('amount', 'money', [
'required' => FALSE,
])
;
}
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Register'
));
}
public function getName()
{
return 'register';
}
}
?>
That "is_a()" function checks that the product variable passed in through the form type class constructor really is an instance of your product entity, so you can be sure that the getTitle() function exists.
Now when you create a new instance of your RegisterType class, you need to pass through an options array including your product. Two conceptual variable names for you to rename here.
<?php
$form = $this->createForm(new RegisterType([
"product" => $instanceOfProductEntityOrNull,
]), $theEntityTheFormWillBeHandling);
?>
Try something like this:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
if ($product->getTitle() == 'SuperProduct') {
$form->add('name', 'text');
}
});
And do not forget the specific use
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
Related
I want to create a form in Symfony. The form needs a collection of task fields which are loaded from the database. Every single field should have a dropdown to select a person who is supposed to do this task.
What I want to do is to map those two entities together. What I have so far is this:
createFormType.php
class CreateFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("dateValidFrom", DateTimeType::class)
->add("dateValidUntil", DateTimeType::class)
->add("generalTasks", CollectionType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
"data_class" => DTOPlan::class
]);
}
}
DTOPlan.php
class DTOPlan
{
public \DateTimeImmutable $dateValidFrom;
public \DateTimeImmutable $dateValidUntil;
public array $tasks;
public function setGeneralTasks(array $generalTasks): void
{
$this->generalTasks = array_fill_keys($generalTasks, null);
}
...getter and setter
}
Controller.php
public function newPlan(Request $request)
{
$generalTasks = $this->generalTaskRep->findAll();
$dtoPlan = new DTOPlan();
$dtoPlan->setGeneralTasks($generalTasks);
$form=$this->createForm(CreateFormType::class, $dtoPlan);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->planRepository->updatePlan($form->getData());
$this->redirectToRoute("app.plan.show");
}
return $this->render("/Plan/plan_create.html.twig", [
"createForm" => $form->createView()
]);
}
The labels are now correct: Form
Now I want to insert a dropdown into them for selecting firstName and lastName of Person
How can I manage that?
class User
{
public $id;
public $username;
public function getCompanies()
{
//stuff
}
}
class Company
{
public $id;
public $name;
public static function getForUser(User $user)
{
//stuff
}
}
I have the above two models and want to create a reusable custom ChoiceType field that will be a select box of filtered Company ids (or objects) based on a User object. If no User is supplied, then have a ChoiceType (select box) of all Company objects.
How do I go about this? I've been going around in circles reading the Symfony docs and can't seem to find any examples similar to my needs.
I can either use getForUser() from the Company class or getCompanies() from the User class (they pretty much do the same thing).
There is no direct relationship between Company and User. They are 'joined' through other objects/tables (Manager/Customer/Contact).
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('company', ChoiceType::class, array(
"choices" => ??????
));
}
public function getParent()
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Company::class,
));
}
}
All help will be appreciated.
I would suggest you to try this link:
https://symfony.com/doc/current/reference/forms/types/entity.html#using-a-custom-query-for-the-entities
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...
$builder->add('users', EntityType::class, array(
'class' => 'AppBundle:Company',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.name', 'ASC'); //or DESC
},
'choice_label' => 'username',
));
Starting with Symfony is quite a learning curve. Even after reading for hours, I cannot get across this presumably simple problem. I want to load a choices-form with values from an entity.
Controller:
namespace AppBundle\Controller
class ItemController extends Controller
{
public function itemAction (Request $request)
{
$myItems = new Itemlist();
//some statements to fill $myItems
$form = $this->createForm (AllitemsType::class, $myItems);
// some more stuff
return $this->render (...);
}
}
Entity:
namespace AppBundle\Entity;
class Itemlist
{
protected $choices;
protected $defaultvalue;
public function __construct ()
{
$choices = array();
}
// all the get and set-methods to fill/read the $choices array and $defaultvalue
}
Form:
namespace AppBundle\Form
class AllitemsType extends AbstractType
{
public function buildForm (FormBuilderInterface $builder, array $options)
{
// and here is my problem: how can I fill next two lines with values from the Itemlist-Entity?
// The Itemlist instance has been build in the controller and is unknown here
$items = ??? // should be 'AppBundle\Entity\Itemlist->$choices
$defaultitem = ??? // should be 'AppBundle\Entity\Itemlist->$defaultvalue
$choices_of_items = array (
'choices' => $items,
'expanded' => true,
'multiple' => false,
'data' => $defaultitem,
);
$builder->add ('radio1', ChoiceType::class, $choices_of_items);
}
}
Any help appreciated,
Wolfram
$builder->add('choices', ChoiceType::class);
should be sufficient as you're binding an entity to the form, the process of getting values and setting them back is automatic. Of course you need to have setter and getter for choices field in AllitemsType
To give a complete answer - part above is the so called "best practice one" - you can also choose one of the following
$items = $options['data'];
or
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$allItems = $event->getData();
$form = $event->getForm();
$form->add('radio1', ChoiceType::class, [
'choices' => $allItems
]);
});
Second one should be preferred as, in options['data'], entity could change during form event's lifetime.
Pass variables with createForm object.
Controller:
namespace AppBundle\Controller
class ItemController extends Controller
{
public function itemAction (Request $request)
{
$myItems = new Itemlist();
$formVars = array("items" => array(1,2,3,4,6), "defaultItems" => 2); // Store variables
^^
//some statements to fill $myItems
$form = $this->createForm (new AllitemsType($formVars), $myItems);
^^
// some more stuff
return $this->render (...);
}
}
Now create constructor in form and set class variables items and defaultitem in form.
Form:
namespace AppBundle\Form
class AllitemsType extends AbstractType
{
$this->items = array();
$this->defaultitem = 0;
public function __construct($itemArr)
{
$this->items = $itemArr['items'];
$this->defaultitem = $itemArr['defaultItems'];
}
public function buildForm (FormBuilderInterface $builder, array $options)
{
$choices_of_items = array (
'choices' => $this->items, // User class variable
'expanded' => true,
'multiple' => false,
'data' => $this->defaultitem, // User class variable
);
$builder->add ('radio1', ChoiceType::class, $choices_of_items);
}
}
It should solve your problem.
I'm currently trying to implement a key-pair value for a form type, which is used together with the FOSRestBundle to allow for sending a request like the following:
{
"user": {
"username": "some_user",
"custom_fields": {
"telephone": "07777",
"other_custom_field": "other custom value"
}
}
}
The backend for this is represented as follows:
User
id, username, customFields
CustomUserField
id, field
CustomUserFieldValue
user_id, field_id, value
I've currently made a custom form as follows:
<?php
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add(
'custom_fields',
'user_custom_fields_type'
)
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Acme\DemoBundle\Entity\User',
'csrf_protection' => false,
)
);
}
public function getName()
{
return 'user';
}
}
And my user_custom_fields_type:
<?php
class CustomUserFieldType extends AbstractType
{
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$fields = $this->em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field) {
$builder->add($field->getField(), 'textarea');
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'invalid_message' => 'The selected custom field does not exist'
)
);
}
public function getParent()
{
return 'collection';
}
public function getName()
{
return 'user_custom_fields_type';
}
}
This keeps giving me the error that there are extra fields. Which are the ones I've added in the CustomUserFieldType. How can I get this working?
Note: this is a simplified version of the actual code, I've tried removing all the irrelevant code.
You need to use form listeners to add dynamic fields to your forms:
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
In your case a PRE_SET_DATA should suffice. Something like this:
$em = $this->em;
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ( $em )
{
// get the form
$form = $event->getForm();
// fetch your data here
$fields = $em->getRepository('AcmeDemoBundle:CustomUserField')->findAll();
foreach($fields as $field)
{
// make sure you add the new fields to $form and not $builder from this event
$form->add($field->getField(), 'textarea');
}
});
I had exactly the same issue and finally solved it by parsing the custom fields manually. If there is another solution, please share :)
In the UserType form:
$builder->addEventListener(FormEvents::POST_SET_DATA,
function (FormEvent $event) use ( $oEm, $oUser )
{
$oForm = $event->getForm();
$aFields = $oEm->getRepository('MyDBBundle:CustomUserField')->findAll();
/** #var CustomUserField $oField */
foreach($aFields as $oField)
{
$oForm->add(
'custom__'.$oField->getKey(),
$oField->getType(),
array(
'label' => $oField->getField(),
'mapped' => false,
'required' => false
)
);
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oUser, 'field' => $oField));
if(null !== $oFieldValue) {
$oForm->get('custom__' . $oField->getKey())->setData($oFieldValue->getValue());
}
}
}
);
Then, in your controller action which handles the request of the submitted form:
// Handle custom user fields
foreach($oForm->all() as $sKey => $oFormData)
{
if(strstr($sKey, 'custom__'))
{
$sFieldKey = str_replace('custom__', '', $sKey);
$oField = $oEm->getRepository('MyDBBundle:CustomUserField')->findOneBy(array('key' => $sFieldKey));
/** #var CustomUserFieldValue $oFieldValue */
$oFieldValue = $oEm->getRepository('MyDBBundle:CustomUserFieldValue')->findOneBy(array('user' => $oEntity, 'field' => $oField));
if($oFieldValue === null)
{
$oFieldValue = new CustomUserFieldValue();
$oFieldValue->setUser($oEntity);
$oFieldValue->setField($oField);
}
$oFieldValue->setValue($oFormData->getData());
$oEm->persist($oFieldValue);
}
}
(Assuming that there is both a "field" property and a "key" in the CustomUserField entity; key is a unique, spaceless identifier for your field and field is the human friendly and readable field label.)
This works so hope it can be helpful. However, wondering if someone has a better solution. :)
What I want to do :
I'm trying to access a field from an embedded formType in a collection.
I can easily access the first level (so getting the collection) with $form->get('childType') but I struggle to access the field embedded in childType.
I tried $form->get('childType')->get('anotherAttr') with no success. IMHO the problem comes from the fact that a Collection is not just a field, and getting('anotherAttr') can't be done without Symfony knowing on what item of the collection I want to do this get. Anyways after a lot of search I haven't find how to tell him I want the first item from the collection.
Here is the code :
The parent class type :
<?php
namespace my\myBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ParentType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('attribute1','text',array("label" => 'attribute 1 :'))
->add('childType','collection',array('type' => new ChildType($options['attrForChild'])));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'my\myBundle\Entity\Parent',
'attrForChild' => null
);
}
public function getName()
{
return 'my_mybundle_childtype';
}
}
The childClassType :
<?php
namespace my\myBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ChildType extends AbstractType
{
private $childAttr;
public function __construct($childAttr=null){
$this->childAttr=$childAttr;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('childAttr','text',array("label" => 'childAttr : ','property_path' => false));
if(isset($this->childAttr)){
$childAttr = $this->childAttr;
$builder->add('childAttrDependantEntity','entity',array("label" => 'RandomStuff : ',
'class' => 'mymyBundle:randomEntity',
'property' => 'randProperty',
'multiple' => false,
'query_builder' => function(\my\myBundle\Entity\randomEntityRepository $r) use ($childAttr) {
return $r->findByChildAttr($childAttr);
}
));
}
$builder->add('anotherAttr','text',array("label" => 'Other attr : '))
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'crri\suapsBundle\Entity\Adresse',
'childAttr' => null
);
}
public function getName()
{
return 'my_mybundle_childtype';
}
}
Also, does the childAttr solution I use is ok? (it is working, but it feels a bit as a hack, is there a cleaner way to do the same thing?). What it is used for = the user gives me a text field, I verify if it exists in database, if it does exist, I add an entityType to the form which is related to this attribute. The goal is that the user will select from a restrain list of elements, instead of all the elements from the database.
EDIT : the controller's corresponding code :
public function parentTypeAddAction(Request $request){
$parentEntity = new ParentEntity();
$parentEntity->addChildEntity(new ChildEntity());
$form = $this->createForm(new ParentType,$parentEntity);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
// Testing (everything I tried)
$test=$form->get('childType')->getAttribute('childAttr');
/**
$test=$form['childAttr'];
$test=$form->get('childAttr'); **/
return $this->container->get('templating')->renderResponse('myMyBundle:Default:test.html.twig',
array('test' => $test));
if($test!=null ){
$anEntity = $em->getRepository('crrisuapsBundle:AnEntity')->find($test);
if($anEntity==null){
$form->get('childType')->get('childAttr')->addError(new FormError("Invalid attribute."));
} else {
$form = $this->createForm(new ParentType,$parentType,array('childAttr' => $test));
$individu->getAdresses()->first()->setAnEntity($anEntity);
}
}
$form->bindRequest($request);
if($request->request->get('CHILDATTRPOST')!='Search attribute'){
if ($form->isValid()) {
$em->persist($parentType);
$em->persist($individu->getChildEntity()->first());
$em->flush();
return $this->redirect($this->generateUrl('myMyBundle_homepage'), 301);
}
}
}
return $this->container->get('templating')->renderResponse('myMyBundle:Default:parentTypeAdd.html.twig',
array('form' => $form->createView()));
}
Thanks to cheesemacfly's suggestions I could figure out how to get it. Here is the solution :
//Getting the childEntities forms as an array
$childArray=$form->get('childType')->getChildren();
//Getting the childEntity form you want
$firstChild=$childArray[0];
//Getting your attribute like any form
$childAttrForm=$childArray[0]->get('childAttr');
$childAttr=$childAttrForm->getData();