symfony 2.3 form getData doesn't work in subforms collections

Angel picture Angel · Sep 18, 2013 · Viewed 10.5k times · Source

I have a form which contains a collection. So I have:

/* my type */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    ->add('name')
    ->add('photos','collection',array(
        'type'=> new PhotoType(),
        'allow_add'=>true));
}

/*Photo Type*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    ->add('photoname')
    ->add('size')
}

But I want to access the data inside the photo, so I tried inside the PhotoType:

$data = $builder->getData();

But it seems that it doesn't work, even if I am editting the form, so the photo collection has data. Why can't I access to the $builder->getData() in a form called by another?? Because I'm trying not to do and eventListener...

Answer

Bernhard Schussek picture Bernhard Schussek · Sep 18, 2013

To understand what is happening here you have to understand data mapping first. When you call

$form->setData(array('photoname' => 'Foobar', 'size' => 500));

the form's data mapper is responsible for taking the given array (or object) and writing the nested values into the fields of the form, i.e. calling

$form->get('photoname')->setData('Foobar');
$form->get('size')->setData(500);

But in your example, you are not dealing with Form, but with FormBuilder objects. FormBuilder is responsible for collecting the configuration of a form and using this information to produce a Form instance. As such, FormBuilder also lets you store the default data for the form. But since it's a simple configuration object only, it will not invoke the data mapper as of yet. For example:

$builder = $factory->createBuilder()
    ->add('photoname')
    ->add('size')
    ->setData(array('photoname' => 'Foobar', 'size' => 500));

print_r($builder->get('photoname')->getData());
print_r($builder->get('size')->getData());

This example will output:

null
null

because data mapping takes place later, when we turn the FormBuilder into a Form instance. We can use this fact to set separate default values for the individual fields:

$builder->add('size', null, array('data' => 100));
// which is equivalent to
$builder->get('size')
    ->setData(100)
    ->setDataLocked(true);

print_r($builder->get('photoname')->getData());
print_r($builder->get('size')->getData());

And the output:

null
100    

Data locking is required to prevent the data mapper from overriding the default data you just stored. This is done automatically if you pass the "data" option.

At last, you will build the form. Now, FormBuilder calls Form::setData() where necessary, which in turn will invoke the data mapper:

$form = $builder->getForm();

// internally, the following methods are called:

// 1) because of the default data configured for the "size" field
$form->get('size')->setData(100);

// 2) because of the default data configured for the main form
$form->setData(array('photoname' => 'Foobar', 'size' => 500));

// 2a) as a result of data mapping
$form->get('photoname')->setData('Foobar');

// 2b) as a result of data mapping (but ignored, because the data was locked)
$form->get('size')->setData(500);