Symfony2 form events and model transformers

后端 未结 3 1985
傲寒
傲寒 2020-12-08 00:57

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!

相关标签:
3条回答
  • 2020-12-08 01:29

    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"

    0 讨论(0)
  • 2020-12-08 01:37

    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.

    0 讨论(0)
  • 2020-12-08 01:55

    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.

    0 讨论(0)
提交回复
热议问题