Form - Accessing submitted/bound data - php

Imagine this form
class CarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('brand', EntityType::class, array(
'class' => 'AppBundle:CarBrand',
'label' => 'Brand',
))
->add('model', EntityType::class, array(
'class' => 'AppBundle:CarModel',
'label' => 'Car model',
'choices' => array(), // <- Depends on brand
));
// ...
}
}
I used some ajax to make the model list depending on brand selection.
I didn't use FormEvents because it was a real pain in the ass for my simple need (I have actually 3 dependencies based on multiple parameters and I already spent an entire day trying to figure out how to make it work with FormEvents)
Now, once the form is submitted, I want to get the bound values from my controller.
/**
* #Route("/context", name="context_selection")
*/
public function carSelectionAction(Request $request)
{
$form = $this->createForm(CarType::class, new Car());
$form->handleRequest($request);
if ($form->isSubmitted()) {
$car = $form->getData();
dump($car); // <- Getting only brand here
}
return $this->render('AppBundle::car-selection.html.twig', array(
'form' => $form->createView()));
}
The dumped data is showing only the CarBrand object bound, I imagine because the selected CarModel is not a valid choice.
How can I get the bound CarModel ?
I'd rather not having to read the request params and load the objects myself

Related

Accessing Entity Passed to Form Builder with Symfony

I am building a form using the Symfony framework and am trying to understand how to pass an instance of an entity to the form builder.
Controller:
$organization = $user->getOrganization();
$form = $this->createForm(OrganizationCourseType::class, $organization);
OrganizationCourseType class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('courses', EntityType::class, [
'class' => Course::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('course')
->andWhere('course.organization = :organization')
->setParameter('organization', $organization);
},
]);
}
However I get the error:
Notice: Undefined variable: organization
How can I access the entity (organization) within the form builder? Do I need to pass it as an option? If so what is the point of including it in the createForm call in the controller?
To address the second part of your question: "what is the point of including it in the createForm call ?"
When you pass an object as the second argument to createFormBuilder you are passing the initial data for the form. Symfony will try to find properties (or getters/setters) that match the form field name in the object and assign its value to the field. Then, on submission, it'll update your model accordingly.
You'd tipically pass the same type of object as the form's data_class, so in your case that would be an OrganizationCourse. You could do something like the following:
$organizationCourse = new OrganizationCourse();
$organizationCourse->setOrganization($user->getOrganization());
$form = $this->createForm(OrganizationCourseType::class, $organizationCourse);
You could select many Courses and assign them to the Organization.
However, this doesn't look like your use case, since OrganizationCourse looks like a relation rather than an entity, so refer to #ehimel's answer.
In your controller, pass the instance of your entity as a third argument to your $form definition line:
$form = $this->createForm(OrganizationCourseType::class, null, ['organization' => $organization]);
Then retrieve it in your FormType class like so:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$organization = $options['organization'];
$builder->add('courses', EntityType::class, [
'class' => Course::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('course')
->andWhere('course.organization = :organization')
->setParameter('organization', $organization);
},
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(['organization']);
}

Object of class AppBundle\Entity\User could not be converted to string Symfony 3

I'm trying to create a form with EntityType but is not running.
This is my BD:
I already got Users and AcademiucProgram, so I want to create a register between User and AcameicProgram.
From my controller it provides a variable to my form which contains the ID of the user.
My controller:
public function indexAction(Request $request,$id){
$ac = new Usersacademi();
$form = $this->createForm(UsersacademiType::class,$ac,array('id'=>$id));
$form->handleRequest($request);
if($form->isValid()){
$ac->setIdacademicprogram($form->get("idacademicprogram")->getData());
$ac->setIduser($form->get("iduser")->getData());
$em = $this->getDoctrine()->getManager();
$em->persist($ac);
$flush = $em->flush();
}
else{
}
return $this->render("AppBundle:admin:apteacher.html.twig", array(
"form" => $form->createView()
));
}
My Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('idacademicprogram', EntityType::class, array(
"required"=>"required",
'class' => 'AppBundle:Academicprogram',
'choice_label' => 'name'
))
->add('iduser', EntityType::class, array("required"=>"required",
"data" =>$options["id"],
'class' => 'AppBundle:User',
"attr"=>array(
"class" => "form-iduser form-control"
)))
->add('Registrar',SubmitType::class, array("attr"=>array(
"class" => "form-submit btn btn-success"
)));
}
So I want to be able to select the AcademicProgram with a Select Option (this is actually running) and then on the second field (idUser) I want that by default is the id of the user selected (that id number is provided by the controller) and then be able to submit this register.
Looks like you forgot to add choice_label in your user form field or you can add a __toString() method inside your User entity.
From the docs:
If left blank, the entity object will be cast to a string and so must have a __toString() method.
By default EntityType already uses entity id as value.

PHP silex Formbuilder and object with array of object

I am using PHP with the Silex framework and after some hours trying to find a satisfying solution I am still blocked with the following problem regarding forms and objects containing array of Objects. The hours spent permitted me to find a working solution but I hope there is a better way to do that with Silex.
In my application I have a User class that is defined like :
class User implements UserInterface
{
private $id;
private $username;
private $displayname;
private $capacities;
//...
}
$capacities variable contains an array of objects from another class (Capacity). A Capacity is a specific role in my app with various information (label, place of the capacity ...) and I have added, in that Capacity class a boolean telling if the Capacity is active for a specific user, when attached to a user via the $capacities array.
At the moment I am able to create the form that looks as I want with the following code :
use Planning\Domain\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, array(
'required' => true,
'label' => "Login (pour Jean Durant → jdurant)"
))
->add('displayname', TextType::class, array(
'label' => "Nom à afficher"
));
$choices = array();
$choicesActive = array();
foreach ($builder->getData()->getCapacities() as $id => $capacity) {
$choices[$capacity->getLabel()] = $capacity->getId();
if ($capacity->getActive()) {
$choicesActive[] = $capacity->getId();
}
}
$builder->add('capacities', ChoiceType::class, array(
'choices' => $choices,
'data' => $choicesActive,
'label' => "Groupes",
'multiple' => True,
'expanded' => True
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
}
public function getName()
{
return 'capacity';
}
}
but the User object I am getting after the form is validated contains for capacities :
[capacities:Planning\Domain\User:private] => Array
(
[0] => 2
[1] => 4
[2] => 1
)
capacities variable contains the list of values for checkboxes that have been checked in the form. The issue is that my User object is not consistant with its definition which says that the capacities property should be an array of Capacity objects. What I am doing at the moment is that I have added the following code to my controller when $userForm->isSubmitted() and $userForm->isValid() :
// Getting the array from the returned user, should contain Capacity object but just contains the IDs.
$capacitiesChecked = $userForm->getData()->getCapacities();
// We regenerate the full array of capacities for this user
$user->setCapacities($app["dao.capacity"]->findAll());
// Then we will activate capacities that have been checked, one by one
foreach ($capacitiesChecked as $capacityChecked) {
$user->getCapacityById($capacityChecked)->setActive(True);
}
This is working and I am happy with it but being new to Silex and the framework world, I am surprized that I have not been able to find an easier way to answer my problem which I believe should be quite common.
I might be missing something from the Silex/Symfony philosophy and I hope someone there will be able to point me to the correct place to get more information or find a solution!
Edit following #dbrumann answer
As it might not be clear how my data is organized, here are the tables in my database :
user
id
username
displayname
capacity
id
label
place
user_capacity
id_user
id_capacity
There might be an issue with the modeling of my project but I have a Capacity class and a User class and User has an attribute with an array containing all the Capacity available in the database and each one of this Capacity object has an active attribute that is set to True if there is an entry in the table user_capacity that links user and capacity. What I would like is ONE form that allows me to properly update data into tables user and user_capacity.
If I understand correctly Capacity is another entity that you want to link. In that case you could use the more specific EntityType and then filter the number of values by using the query_builder attribute as described in the documentation:
$builder->add('users', EntityType::class, array(
'class' => Capacity::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('capacity')
->where('capacity.active = 1')
->orderBy('capacity.id', 'ASC');
},
'choice_label' => 'id',
));
I have found a solution that I think is acceptable and might be useful for someone landing on this page. So what I have done is that I have changed my User class so that the $capacities attribute now contains only Capacity objects that are related to the user. But to get all the capacities available on the form, I am passing them as an option (allCapacities) and iterating over them to find which one are present in User->capacities to check them in the form.
The updated class used to build the form is as following:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class, array(
'required' => true,
'label' => "Login (pour Jean Durant → jdurant)"
))
->add('displayname', TextType::class, array(
'label' => "Nom à afficher"
));
$choices = array();
$choicesActive = array();
foreach ($options["allCapacities"] as $id => $capacity) {
$choices[] = $capacity;
if ($builder->getData()->hasCapacity($capacity)) {
$choicesActive[] = $capacity;
}
}
$builder->add('capacities', ChoiceType::class, array(
'choices' => $choices,
'data' => $choicesActive,
'choice_label' => function($category, $key, $index) {
return $category->getLabel();
},
'label' => "Groupes",
'multiple' => True,
'expanded' => True,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
$resolver->setRequired(array(
'allCapacities',
));
}
public function getName()
{
return 'capacity';
}
}
This is working as expected and does seem to be overcomplicated but there might be easier solution by changing the design, any comment on this would then be welcome!

Set Default value of choice field Symfony FormType

I want from the user to select a type of questionnaire, so I set a select that contains questionnaires types.
Types are loaded from a an entity QuestionType .
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'))
->add('type', 'hidden')
;
What am not able to achieve is to set a default value to the resulted select.
I have googled a lot but I got only preferred_choice solution which works only with arrays
I made it by setting a type in the newAction of my Controller I will get the seted type as default value.
public function newAction($id)
{
$entity = new RankingQuestion();
//getting values form database
$em = $this->getDoctrine()->getManager();
$type = $em->getRepository('QuizmooQuestionnaireBundle:QuestionType')->findBy(array('name'=>'Ranking Question'));
$entity->setQuestionType($type); // <- default value is set here
// Now in this form the default value for the select input will be 'Ranking Question'
$form = $this->createForm(new RankingQuestionType(), $entity);
return $this->render('QuizmooQuestionnaireBundle:RankingQuestion:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
'id_questionnaire' =>$id
));
}
You can use data attribute if you have a constant default value (http://symfony.com/doc/current/reference/forms/types/form.html)
but it wont be helpful if you are using the form to edit the entity ( not to create a new one )
If you are using the entity results to create a select menu then you can use preferred_choices.
The preferred choice(s) will be rendered at the top of the list as it says on the docs and so the first will technically be the default providing you don't add an empty value.
class MyFormType extends AbstractType{
public function __construct($foo){
$this->foo = $foo;
}
$builder
->add('questionType', 'entity', array(
'class' => 'QuizmooQuestionnaireBundle:QuestionType',
'property' => 'questionTypeName',
'multiple' => false,
'label' => 'Question Type'
'data' => $this->foo))
->add('type', 'hidden')
;
}
In controller
$this->createForm(new MyFormType($foo));
The accepted answer of setting in the model beforehand is a good one. However, I had a situation where I needed a default value for a certain field of each object in a collection type. The collection has the allow_add and allow_remove options enabled, so I can't pre-instantiate the values in the collection because I don't know how many objects the client will request. So I used the empty_data option with the primary key of the desired default object, like so:
class MyChildType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('optionalField', 'entity', array(
'class' => 'MyBundle:MyEntity',
// Symfony appears to convert this ID into the entity correctly!
'empty_data' => MyEntity::DEFAULT_ID,
'required' => false,
));
}
}
class MyParentType
extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('children', 'collection', array(
'type' => new MyChildType(),
'allow_add' => true
'allow_delete' => true,
'prototype' => true, // client can add as many as it wants
));
}
}
Set a default value on the member variable inside your entity (QuestionType), e.g.
/**
* default the numOfCourses to 10
*
* #var integer
*/
private $numCourses = 10;

Can you 'extend' form classes?

I am creating form classes for my forms, but cannot figure out how to 'extend' them.
For example, I have a CustomerType form class, and an EmailType form class. I could add the EmailType directly into my CustomerType
$builder->add('emails', 'collection', array(
'type' => new EmailType(),
'allow_add' => true,
'by_reference' => false
));
but I'd prefer to do this in the controller, so that my CustomerType form class contains only customer information. I feel this is more modular and reusable, since sometimes I want my user to be able to edit only Customer details, and others both Customer details as well as Email objects associated with that customer. (For example, in the first case when viewing a customer's work order, and in the second when creating a new customer).
Is this possible? I'm thinking something along the lines of
$form = $this->createForm(new CustomerType(), $customer);
$form->add('emails', 'collection', ...)
in my controller.
You could pass an option (say "with_email_edition") to your form when it's created that would tell if the form should embed the collection or not.
In the Controller:
$form = $this->createForm( new CustomerType(), $customerEntity, array('with_email_edition' => true) );
In the form:
Just add the option in the setDefaultOptions:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'with_email_edition' => null,
))
->setAllowedValues(array(
'with_email_edition' => array(true, false),
));
}
and then check in the "buildForm" the value of this option,and add a field based on it:
public function buildForm(FormBuilderInterface $builder, array $options)
{
if( array_key_exists("with_email_edition", $options) && $options['with_email_edition'] === true )
{
//Add a specific field with $builder->add for example
}
}

Categories