Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Form: Avoid setting null to non submitted field

Tags:

symfony-2.1

I've got a simple model (simplified of source):

class Collection
{
    public $page;
    public $limit;
}

And a form type:

class CollectionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('page', 'integer');
        $builder->add('limit', 'integer');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'FSC\Common\Rest\Form\Model\Collection',
        ));
    }
}

My controller:

public function getUsersAction(Request $request)
{
    $collection = new Collection();
    $collection->page = 1;
    $collection->limit = 10;

    $form = $this->createForm(new CollectionType(), $collection)
    $form->bind($request);

    print_r($collection);exit;
}

When i POST /users/?form[page]=2&form[limit]=20, the response is what i expect:

Collection Object
(
    [page:public] => 2
    [limit:public] => 20
)

Now, when i POST /users/?form[page]=3, the response is:

Collection Object
(
    [page:public] => 3
    [limit:public] =>
)

limit becomes null, because it was not submitted.

I wanted to get

Collection Object
(
    [page:public] => 3
    [limit:public] => 10 // The default value, set before the bind
)

Question: How can i change the form behaviour, so that it ignores non submitted values ?

like image 716
AdrienBrault Avatar asked Jul 27 '12 12:07

AdrienBrault


2 Answers

If is only a problem of parameters (GET parameters) you can define the default value into routing file

route_name:
pattern: /users/?form[page]={page}&form[limit]={limit}
defaults: { _controller: CompanyNameBundleName:ControllerName:ActionName, 
                         limit:10 }

An alternative way could be to use a hook (i.e. PRE_BIND) and update manually that value into this event. In that way you haven't the "logic" spreaded into multi pieces of code.

Final code - suggested by Adrien - will be

<?php

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;

class IgnoreNonSubmittedFieldSubscriber implements EventSubscriberInterface
{
    private $factory;

    public function __construct(FormFactoryInterface $factory)
    {
        $this->factory = $factory;
    }

    public static function getSubscribedEvents()
    {
        return array(FormEvents::PRE_BIND => 'preBind');
    }

    public function preBind(FormEvent $event)
    {
        $submittedData = $event->getData();
        $form = $event->getForm();

        // We remove every child that has no data to bind, to avoid "overriding" the form default data
        foreach ($form->all() as $name => $child) {
            if (!isset($submittedData[$name])) {
                $form->remove($name);
            }
        }
    }
}
like image 108
DonCallisto Avatar answered Nov 16 '22 08:11

DonCallisto


Here's a modification of the original answer. The most important benefit of this solution is that validators can now behave as if the form post would always be complete, which means there's no problems with error bubbling and such.

Note that object field names must be identical to form field names for this code to work.

<?php
namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;

class FillNonSubmittedFieldsWithDefaultsSubscriber implements EventSubscriberInterface
{
    private $factory;

    public function __construct(FormFactoryInterface $factory)
    {
        $this->factory = $factory;
    }

    public static function getSubscribedEvents()
    {
        return array(FormEvents::PRE_BIND => 'preBind');
    }

    public function preBind(FormEvent $event)
    {
        $submittedData = $event->getData();
        $form = $event->getForm();

        // We complete partial submitted data by inserting default values from object
        foreach ($form->all() as $name => $child) {
            if (!isset($submittedData[$name])) {
                $obj = $form->getData();

                $getter = "get".ucfirst($name);
                $submittedData[$name] = $obj->$getter();
            }
        }
        $event->setData($submittedData);

    }
}
like image 26
TomiS Avatar answered Nov 16 '22 10:11

TomiS