I\'m getting tied in knots trying to wrestle with Symfony2\'s form builders, events and transformers... hopefully somebody here is more experienced and can help out!
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"
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.