使用数据变形金刚& symfony3上的FormEvents(PRE_SUBMIT)在控制器

时间:2017-12-08 21:37:48

标签: php forms symfony

问题:

我有一个实体合同,它与一个,一个实体客户端相关联。要选择哪个客户端链接到正在创建的合同,我将通过AJAX搜索呈现select2,将一些客户端加载到组件上。表示客户端的元素必须是ChoiceType。

在我的后端,我需要:

  • FormEvent PRE_SUBMIT以使用所提交的选项覆盖ChoiceType表单元素。
  • DataTransformer,它将提交的选项的ID转换为实际的实体客户端。
Data Transformers Docs中的示例相比,所需的结构
ClientesBundle/
├── Controller
│   └── DefaultController.php
├── DataTransformer
│   └── ClienteTransformer.php
├── Entity
│   ├── Cliente.php
│   └── Contrato.php
├── Form
│   └── ClienteType.php
├── Repository
│   ├── ClienteRepository.php
│   └── ContratoRepository.php
└── Resources
    ├── config
    │   ├── routing.yml
    │   └── services.yml
    └── views
        └── ...

在这个结构中,我会在ClienteTransformer.php中使用ClienteType.php并直接在DefaultController中调用

$form = $this->createFormBuilder(Cliente::class)->add('titular', ClienteType::class);
$form->getForm();

我遇到的主要问题:

  1. 上述提到无法实现将这两个元素添加到控制器中的表单,因为PRE_SUBMIT FormEvent将覆盖->addModelTransformer($clientTransformer);导致错误。由此得出,我们无法定义CallbackTransformer内联,或其他不涉及this post中公开的解决方案的选项。
  2. Symfony文档中给出的示例对于我在渲染之前如何创建,检索或修改表单并不清楚。在"How to Create a Custom Form Field Type"中,我可以理解所有内容,直到使用字段类型,这是在另一个自定义字段中完成的:我应该如何直接在控制器中使用自定义字段类型?就像我一样已定义的类型。以下代码有效。但是,如果我将TextType更改为ClienteType(这是我的自定义字段类型),我会收到错误 - Expected argument of type "object, array or empty", "string" given - 。 DefaultController.php

    中的注释指出了引发错误(或向我显示)的确切行
    public function buildFormContrato($entity = null)
    {
        $logger = $this->get('logger');
    
        $form = $this->createFormBuilder($entity ?? new Contrato());
        $form->add('titular', TextType::class);
    
        return $form;
    }
    
  3. 的src / ClientesBundle /窗体/ ClienteType.php

    <?php
    
    namespace ClientesBundle\Form;
    
    use ...
    
    class ClienteType extends AbstractType
    {
        protected $em;
    
        public function __construct(EntityManagerInterface $em)
        {
            $this->em = $em;
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $clienteTransformer = new ClienteTransformer($this->em);
    
            $builder
                ->add('titular', ChoiceType::class,
                   ['placeholder' => 'Selecciona un cliente']
                );
    
            $builder->get('titular')
                ->addModelTransformer($clienteTransformer);
    
            $builder->addEventListener(FormEvents::PRE_SUBMIT,
                function (FormEvent $event)
                {
                    $data = $event->getData();
                    $form = $event->getForm();
    
                    $form->add('titular', ClienteType::class,
                        ['choices' => [
                            $this->em->getRepository(Cliente::class)
                                ->find($data['titular'])
                            ]
                        ]
                    );
                });
    
            return $builder;
    
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => Cliente::class,
                'compound' => true // Set to true tipped by error 'You cannot add children to a simple form. Maybe you should set the option "compound" to true?'
            ));
        }
    
        public function getParent()
        {
            return ChoiceType::class;
        }
    
    
    }
    

    的src / ClientBundle / DataTransformer / ClienteTransformer.php

    <?php
    
    namespace ClientesBundle\DataTransformer;
    
    use ...
    
    class ClienteTransformer implements DataTransformerInterface
    {
    
        private $em;
    
        public function __construct(EntityManagerInterface $em)
        {
            $this->em = $em;
        }
    
        /**
         * Transforms an object (cliente) to a string (string).
         *
         * @param  Cliente|null $cliente
         * @return string
         */
        public function transform($cliente)
        {
            if (null === $cliente) {
                return '';
            }
    
            return $cliente->getDni();
        }
    
        /**
         * Transforms a string (dni) to an object (cliente).
         *
         * @param  string $issueNumber
         * @return Cliente|null
         * @throws TransformationFailedException if object (cliente) is not found.
         */
        public function reverseTransform($clienteDni)
        {
            // no issue number? It's optional, so that's ok
            if (!$clienteDni) {
                return null;
            }
    
            $cliente = $this->em
                ->getRepository(Cliente::class)
                // query for the issue with this id
                ->find($clienteDni)
            ;
    
            if (null === $cliente) {
                // causes a validation error
                // this message is not shown to the user
                // see the invalid_message option
                throw new TransformationFailedException(sprintf(
                    'El cliente con dni "%s" no existe!',
                    $clienteDni
                ));
            }
    
            return $cliente;
        }
    }
    

    的src / ClientBundle /资源/配置/ services.yml

    services:
        clientes.form.type:
            class: ClientesBundle\Form\ClienteType
            tags:
                - { name: form.type, alias: clienteType }
            arguments: [ '@doctrine.orm.entity_manager', '@form.factory']
    

    的src / ClientBundle /控制器/ DefaultController

    <?php
    
    namespace ClientesBundle\Controller;
    
    use ...
    class DefaultController extends Controller
    {
        ...
    
        public function buildFormContrato($entity = null)
        {
    
            $logger = $this->get('logger');
    
            $form = $this->createFormBuilder($entity ?? new Contrato());
            $form->add('titular', ClienteType::class);
    
            return $form;
        }
    
        ...
    
        public function nuevoContratoAction(Request $request)
        {
    
            $logger = $this->get('logger');
    
            $form = $this->buildFormContrato()
                ->add('finalizar', SubmitType::class) //THIS IS THE LINE I GET THE ERROR ON.
                ->getForm();
    
            $form->handleRequest($request);
    
            if ($form->isSubmitted() && $form->isValid())
            { ... }
    
        return $this->render(
            'ClientesBundle:Default:nuevoContrato.html.twig',
            ['form' => $form->createView()]);
        }
    }
    

    我主要使用以下文件解决了这个问题:

    Create a custom Field Type

    Data Transformers

    Symfony2 form events and model transformers

    Symfony data transformer with a collection field

1 个答案:

答案 0 :(得分:0)

看一下示例:类IssueSelectorType扩展AbstractType https://symfony.com/doc/current/form/data_transformers.html

class TitularType extends AbstractType
{
    private $transformer;

    public function __construct(ClienteTransformer $transformer)
    {
        $this->transformer = $transformer;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addModelTransformer($this->transformer);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'invalid_message' => 'The selected NIF does not exist',
        ));
    }

    public function getParent()
    {
        return TextType::class;
    }
}

将从您的服务定义中调用该构造:

services:
    clientes.form.titular:
        class: ClientesBundle\Form\TitularType
        arguments: [ @clientes.transformer.client_nif ]
        tags:
            - { name: form.type, alias: titular }