Symfony2 form events and model transformers
Inheriting $builder
from the parent scope using an annonymous function as event handler would also work.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) use ($builder) {
$data = $event->getData();
if (empty($data['linkedFoo'])) {
return;
}
$builder->add('linkedFoo', 'choice', [
'choices' => $this->fooRepo->getListAsArray(
$data->getLinkedfoo()->getId()
),
]);
$builder->get('linkedFoo')
->addModelTransformer(
new FooTransformer()
);
});
For anyone that is still looking for a better way to add/re-add a Model Transformer inside form events, I think that the best solution is the one from this post all credits go to @Toilal for this brilliant solution
So if you implement the ModelTransformerExtension and define it as a service, and change some code, for example, from
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'onPreSetData')
);
$builder->add(
$builder
->create('customer', TextType::class, [
'required' => false,
'attr' => array('class' => 'form-control selectize-customer'),
])
->addModelTransformer(new CustomerToId($this->customerRepo))
)
;
}
to something like:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
array($this, 'onPreSetData')
);
$builder->add('customer', TextType::class, [
'required' => false,
'attr' => array('class' => 'form-control selectize-customer'),
'model_transformer' => new CustomerToId($this->customerRepo),
]
)
;
}
And now if we remove and re-add the desired field inside the eventlistener function, the Model Transformer for the field will not be lost.
protected function onPreSetData(FormEvent $event)
{
$form = $event->getForm();
$formFields = $form->all();
foreach ($formFields as $key=>$value){
$config = $form->get($key)->getConfig();
$type = get_class($config->getType()->getInnerType());
$options = $config->getOptions();
//you can make changes to options/type for every form field here if you want
if ($key == 'customer'){
$form->remove($key);
$form->add($key, $type, $options);
}
}
}
Note that this is a simple example. I've used this solution to easily process a form to have multiple field states in different places.
Your listener looks (almost :) ) ok.
Just use PRE_SUBMIT.
In that case, $event->getData()
will be the raw form data (an array) that is sent.
$selectedFoo
will potentailly contain "other".
If it is the case, you will replace the "short" 'choice' field with a full one, by using formFactory in the listener.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$data = $event->getData();
if (empty($data['linkedFoo']) || $data['linkedFoo'] !== 'other') {
return;
}
// now we know user choose "other"
// so we'll change the "linkedFoo" field with a "fulllist"
$event->getForm()->add('linkedFoo', 'choice', array(
'choices' => $fullList, // $em->getRepository('Foo')->getFullList() ?
));
$event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooTransformer);
});
You asked so much questions I don't know where to start.
Concerning dataTransformers: until you want to transform raw data into a different representation ("2013-01-01" -> new DateTime("2013-01-01")), then you don't need transformers.
Thanks to the ideas from sstok (on github), I think I've got it working now. The key is to create a customised Form Type and then use that to add the ModelTransformer.
Create the custom Form Type:
namespace Caponica\MagnetBundle\Form\Type;
use ...
class FooShortlistChoiceType extends AbstractType {
protected $em;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$fooTransformer = new FooToStringTransformer($this->em);
$builder
->addModelTransformer($fooTransformer)
;
}
public function getParent() {
return 'choice';
}
public function getName() {
return 'fooShortlist';
}
}
Create a service definition for the new Type:
company_project.form.type.foo_shortlist:
class: Company\ProjectBundle\Form\Type\FooShortlistChoiceType
tags:
- { name: form.type, alias: fooShortlist }
arguments:
- @doctrine.orm.entity_manager
The main form's code now looks something like this:
namespace Company\ProjectBundle\Form\Type;
use ...
class FancyFormType extends AbstractType {
private $fooRepo;
public function __construct(FooRepository $fooRepo)
{
$this->fooRepo = $fooRepo;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
/** @var Bar $bar */
$bar = $builder->getData();
$fooTransformer = new FooToStringTransformer($options['em']);
$builder
->add('linkedFoo', 'fooShortlist', array(
'choices' => $this->fooRepo->getListAsArray(
$bar->getLinkedfoo()->getId()
),
))
;
$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
/** @var EntityManager $em */
$em = $event->getForm()->getConfig()->getOption('em');
$data = $event->getData();
if (empty($data['linkedFoo'])) return;
$selectedFoo = $data['linkedFoo'];
$event->getForm()->add('linkedFoo', 'fooShortlist', array(
'choices' => $em->getRepository('CaponicaMagnetBundle:FooShortlist')->getListAsArray($selectedFoo),
'label' => 'label'
));
});
// ...
}
// ...
}
The key is that this method allows you to embed the ModelTransformer within the custom field type so that, whenever you add a new instance of this type it automatically adds the ModelTransformer for you and prevents the previous loop of "can't add a field without a transformer AND can't add a transformer without a field"