Provide default data in collection child form - forms

I have a nested form with prototype feature in Symfony 2. Here is the parent form which contains the collection:
$builder
->add('rooms', 'collection', array(
'type' => new RoomForm(),
'allow_add' => true,
'allow_delete' => true,
'data' => array(new RoomForm()),
))
As you can see, no data_class is defined. After the form submission $form->getData() correctly return associative array.
RoomForm is a simple form class composed by two fields:
$builder
->add(
$builder->create('dateAvailabilityStart', 'text', array(
'label' => 'label.from'
)))
->add(
$builder->create('dateAvailabilityEnd', 'text', array(
'label' => 'label.until'
)))
I would like find a way to populate my collection with existing RoomForm (for edit mode) and associate data in correct fields.
Any ideas?

You could do it from within your controller. Given that above form type is named as RoomFormCollection you could do something like this:
// This should be an array
$rooms = ... // Either from database or...
$form = $this->createForm(new RoomFormCollection(), array(
'rooms' => rooms
));
Another thing, 'data' => array(new RoomForm()), is not valid. RoomForm as its name suggests is a form type, not data struct. You should remove it...
Hope this helps...

Related

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

Symfony2: Form throws "Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be an array, object given" on submit

I'm using FOSUserBundle in one of my projects.
I've build a form based on the object Employee (that has manytomany with RoleGroup).
Here is the form (part of it):
$builder->add('groups', 'entity', array(
'class' => 'MMAAuthBundle:RoleGroup',
'choices' => $this->groups,
'property' => 'name',
'label' => 'Groups',
'expanded' => true,
'attr' => array("multiple" => true)
));
When I submit the form, I get this error in the Profiler:
at ErrorHandler ->handle ('4096', 'Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be an array, object given, called in /home/mihai/intranet/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 528 and defined', '/home/mihai/intranet/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php', '47', array())
in /home/mihai/intranet/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php at line 47
How can I make the form return an ArrayCollection, not a RoleGroup object?
I had exactly this problem before, but now I'm stuck here.
Your form is currently not a multiple form and therefore passes a single RoleGroup object instead of an array of RoleGroup objects to the constructor of the Collection.
multiple is a form-option ... and not an HTML-attribute. Therefore ...
$builder->add('groups', 'entity', array(
// This would only render a multiple="true" inside the fields HTML tag
'attr' => array("multiple" => true)
... should be ...
$builder->add('groups', 'entity', array(
// multiple option not wrapped by attribute is correct
"multiple" => true

Remove null values coming from empty collection form item

I'm trying to implement a ManyToMany relation in a form between 2 entities (say, Product and Category to make simpe) and use the method described in the docs with prototype and javascript (http://symfony.com/doc/current/cookbook/form/form_collections.html).
Here is the line from ProductType that create the category collection :
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category',
'required' => false),
'allow_add' => true,
'allow_delete' => true,
));
When I had a new item, a new select appear set to the empty value 'Select a category'. The problem is that if I don't change the empty value, it is sent to the server and after a $form->bind() my Product object get some null values in the $category ArrayCollection.
I first though to test the value in the setter in Product entity, and add 'by_reference'=>false in the ProductType, but in this case I get an exception stating that null is not an instance of Category.
How can I make sure the empty values are ignored ?
Citing the documentation on 'delete_empty':
If you want to explicitly remove entirely empty collection entries from your form you have to set this option to true
$builder->add('categories', 'collection', array(
'type' => 'entity',
'options' => array(
'class' => 'AppBundle:Category',
'property'=>'name',
'empty_value' => 'Select a category'),
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
));
Since you use embedded forms, you could run in some issues such as Warning: spl_object_hash() expects parameter 1 to be object, null given when passing empty collections.
Removing required=>false as explained on this answer did not work for me.
A similar issue is referenced here on github and resolved by the PR 9773
I finally found a way to handle that with Event listeners.
This discussion give the meaning of all FormEvents.
In this case, PRE_BIND (replaced by PRE_SUBMIT in 2.1 and later) will allow us to modify the data before it is bind to the Entity.
Looking at the implementation of Form in Symfony source is the only source of information I found on how to use those Events. For PRE_BIND, we see that the form data will be updated by the event data, so we can alter it with $event->setData(...). The following snippet will loop through the data, unset all null values and set it back.
$builder->addEventListener(FormEvents::PRE_BIND, function(FormEvent $event){
$data = $event->getData();
if(isset($data["categories"])) {
foreach($data as $key=>$value) {
if(!isset($value) || $value == "")
unset($data[$key]);
}
$event->setData($data);
});
Hope this can help others !
Since Symfony 3.4 you can pass a closure to delete_empty:
$builder
->add('authors', CollectionType::class, [
'delete_empty' => function ($author) {
return empty($author['firstName']);
},
]);
https://github.com/symfony/symfony/commit/c0d99d13c023f9a5c87338581c2a4a674b78f85f

Pushing an option in a nested form with Symfony 2

I'm working on a form using select boxes. These boxes stand for scales which defer among countries.
Therefore, I created a service which generates custom values to feed the select box according to the user's preferences.This works fine with a first level form:
Controller code:
$form = $this->createForm(new formType, $entity, array(
// Getting the service
'myScales' => $this->get('myBnd.scales'),
// Getting user's scale preference
'scalesLocale' => $this->get('security.context')->getToken()->getUser()->getScaleLng(),
));
Then, I got all I need in the formType to display the customized select:
public function buildForm(FormBuilder $builder, array $options) {
$scaleSelect = array();
// Here is a custom code using $options['myScales'] and $options[scalesLocale']
// This builds the relevant $scaleSelect
// ....
$builder
->add('scale', 'choice', array(
'choices' => $scaleSelect
))
->add('subscale', 'collection', array(
'type' => new subType,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
Then I need to define the nested subType. It also requires a customed select box. How can I send it the variable $scaleSelect in order to generate the (same) appropriate select box ?
quickest option that crosses my mind:
->add('subscale', 'collection', array(
'type' => new Subtype($scaleSelect),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
then in your Subtype class:
function __construct($choices) {
$this->choices = $choices;
}
after that you can access your passed choices in buildform with $this->choices.
Hope it helps.

how to change silex form factory name attribute

How do i change the default name attribute for an input in my form that is created using the form factory?
here is an example of the simple form i am using:
$form = $app['form.factory']->createBuilder('form')
->add('image','file)
->add('longitude', 'hidden')
->add('latitude', 'hidden')
->getForm();
i have tried putting the attributes into an array without successfully changing the name, although with this method i could change the label or class etc:
->add('latitude', 'text', array('attr'=>array("name"=>'newname')))
it seems like a very simple request to be able to change the name of an input, so you would have thought there would be an obvious way to do it. With the code above it would still show the name as name=form[latitude]
Use createNamedBuilder instead createBuilder to overwrite the fields name. The name will be the first argument in add function.
$personal_form = $app['form.factory']->createNamedBuilder(null, 'form')
->add('name', 'text', array(
'label' => 'Nombre',
'data' => 'Nombre'
))
->add('surname', 'text', array(
'label' => 'Apellidos',
'data' => 'Apellidos'
))
->add('email', 'email', array(
'label' => 'E-mail',
'data' => 'E-mail'
))
->getForm();