Florent
Florent

Reputation: 826

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 ?

Upvotes: 2

Views: 7549

Answers (3)

IluTov
IluTov

Reputation: 6852

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

Upvotes: 1

Romain
Romain

Reputation: 6410

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

Upvotes: 8

Florent
Florent

Reputation: 826

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 !

Upvotes: 7

Related Questions