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...
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);
As Bernhard indicated, listeners are the only way to do this because the data is not available in the sub form yet. I used the eventListener to solve a similar requirement. Below is a simplified version of my code that I hope will be helpful:
I have a parent form for my View
entity which has a lot of fields, as well as collection of other forms. One of the sub forms is for an associated entity ViewVersion
, which actually needs to load another form collection for a dynamic entity that is the content type associated with the View
. This content type could by one of many different types of entities, e.g Article, Profile, etc. So I need to find out what contentType is set in the View
data and then find the dynamic path to that bundle, and include that formType.
Once you know how to do it, it's actually easy!
class ViewType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Basic Fields Here
// ...
// ->add('foo', 'text')
// ...
// Load a sub form type for an associated entity
->add('version', new ViewVersionType())
;
}
}
class ViewVersionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Basic Fields Here
// ...
// ->add('foo', 'text')
// ...
;
// In order to load the correct associated entity's formType,
// I need to get the form data. But it doesn't exist yet.
// So I need to use an Event Listener
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
// Get the current form
$form = $event->getForm();
// Get the data for this form (in this case it's the sub form's entity)
// not the main form's entity
$viewVersion = $event->getData();
// Since the variables I need are in the parent entity, I have to fetch that
$view = $viewVersion->getView();
// Add the associated sub formType for the Content Type specified by this view
// create a dynamic path to the formType
$contentPath = $view->namespace_bundle.'\\Form\\Type\\'.$view->getContentType()->getBundle().'Type';
// Add this as a sub form type
$form->add('content', new $contentPath, array(
'label' => false
));
});
}
}
That's it. I'm new to Symfony, so the idea of doing everything in an EventListener is foreign to me (and seems unnecessarily complex). But I hope once I understand the framework better, it will seem more intuitive. As this example indicates, it's not that complicated to do it with an Event Listener, you just wrap your code in that closure (or put it in it's own separate function as described in the docs).
I hope that helps someone!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With