Symfony2 - 如何在EventListeners中使用Data Transformer

  • I need to use an Event Listener since I need different things to be displayed in my form whether it is new or an already existing entity. I can manage that.

  • I need a Data Transformer, to display the entity as text, which is something I also can do.

=> However, I fail in doing the 2 (Data Transformer+Event Listener) at the same time. I get the error: "Error: Call to a member function add() on a non-object". If I replace $builder par $form, I get the following error: Attempted to call an undefined method named "create" of class "Symfony\Component\Form\Form".

That works:

 $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
        $element = $event->getData();
        $form = $event->getForm();
        if (!$element || null === $element->getId()) { //new entity
            $form->add(...);
        } else { //exist already
            //...
        }

That also works:

$builder->add( $builder->create('element', 'text')->addModelTransformer($transformer));

But I cannot make the 2 working at the same time.

    $transformer = new ElementObjToStringTransformer($this->em);
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
        $element = $event->getData();
        $form = $event->getForm();
        if (!$element || null === $element->getId()) { //new entity
            $builder->add( $builder->create('element', 'text')->addModelTransformer($transformer));
        } else { //exist already
            //...
        }
    });

Short answer: You can´t add the transformer inside the listener because the form is already locked.

Long answer: There are some solutions. The most common, at least for me, is to a create a Custom form type where you add your transformer. Then you add your custom form how you would normally do in the event listener:

class ElementCustomType extends AbstractType {

    private $em;

    public function __construct(EntityManager $entityManager)
    {
        $this->em = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {

        $builder
            ->addModelTransformer(new ElementObjToStringTransformer($this->em))
        ;
    }

    public function getParent() {
        return 'text';
    }

    public function getName() {
        return 'elementCustom';
    }
}

Define your form as a service:

 app.form.type.custom_element:
    class: AppBundle\Form\Type\ElementCustomType
    arguments: [@doctrine.orm.entity_manager]
    tags:
        - { name: form.type, alias: elementCustom }

Use the form in the listener as your would normally do:

$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
        $element = $event->getData();
        $form = $event->getForm();
        $form->add('element', 'elementCustom')
    });

I have wrote a Form Extension to support this use case without changing type of fields.

1) Create extension class.

namespace AppBundle\Form\Extension\ModelTransformerExtension

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ModelTransformerExtension extends AbstractTypeExtension {

    public function getExtendedType() {
        return FormType::class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        parent::buildForm($builder, $options);

        if (isset($options['model_transformer'])) {
            $builder->addModelTransformer($options['model_transformer']);
        }
    }

    public function configureOptions(OptionsResolver $resolver) {
        parent::configureOptions($resolver);

        $resolver->setDefaults(array('model_transformer' => null));
    }
}

2) Register this extension as an application service.

services:
    app.form.extension.model_transformer:
        class: AppBundle\Form\Extension\ModelTransformerExtension
        tags:
            - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }

3) Define model_transformer option to any DataTransformer implement when building the form.

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function ($event) {
        $builder = $event->getForm();

        $builder->add('myField', TextType::class, array(
            'model_transformer' => new MyModelTransformer()
        ));
    });
}

This idea can be extended to support addViewTransformer method as well.