Symfony2 形成事件和模型转换器

2022-08-30 18:28:56

我正在纠结,试图与Symfony2的表单构建器,事件和变形金刚摔跤......希望这里有人更有经验,可以帮忙!

我有一个表单字段(选择下拉列表),其中包含一些映射到实体的值(候选列表)。其中一个选项是“其他”。假设目前没有AJAX,当用户提交表单时,我想检测他们是否选择了“其他”(或不在候选列表中的任何其他选项)。如果他们选择了这些选项之一,则应显示完整的选项列表,否则只需显示候选列表。应该很容易,对吧?;)

所以,我有我的表单类型,它显示基本的候选名单就好了。代码如下所示:

namespace Company\ProjectBundle\Form\Type;

use ...

class FancyFormType extends AbstractType {
    private $fooRepo;

    public function __construct(EntityManager $em, 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($builder
                ->create('linkedFoo', 'choice', array(
                    'choices' => $this->fooRepo->getListAsArray(
                        $bar->getLinkedfoo()->getId()
                    ),
                ))
                ->addModelTransformer($fooTransformer)
            )
        ;

        // ...

    }

    // ...
}

现在,我想检查提交的值,以便按如下方式使用表单事件侦听器。

public function buildForm(FormBuilderInterface $builder, array $options) {
    // ... This code comes just after the snippet shown above

    $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', 'choice', array(
            'choices' => $em
                ->getRepository('CompanyProjectBundle:FooShortlist')
                ->getListAsArray($selectedFoo)
            ,
        ));
        //@todo - needs transformer?
    });
}

但是,它会失败,并显示如下错误消息:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int in \path\to\project\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

我推测这个错误是因为当被覆盖时,它删除了?我在事件关闭时尝试了各种方法来访问构建器,但这似乎不起作用(返回值是意外的)。除了?还是我在这里的方法有更根本的问题?linkedFoomodelTransformer$event->getForm()->add()

基本上,我不想弄乱字段的配置/转换器/标签,除了更改可用的选择...有没有其他方法可以做到这一点?例如,类似 ?linkedFoo$form->getField()->updateChoices()

提前感谢您提供的任何帮助!

C

附言:有没有比Symfony网站上更好的表格,事件等文档或讨论?例如,PRE_SET_DATA、PRE_SUBMIT、SUBMIT 等之间的区别是什么?他们什么时候被解雇?它们应该用于什么?继承如何与自定义表单域一起使用?什么是表单和构建器,它们如何交互以及何时应处理它们?如何,何时以及为什么应该使用可以通过访问的FormFactory?等。。$form->getConfig()->getFormFactory()


编辑:为了回应Florian的建议,这里有一些关于尝试但不起作用的事情的更多信息:

如果您尝试在事件中获取表单生成器,如下所示:

/** @var FormBuilder $builder */
$builder = $event->getForm()->get('linkedFoo')->getConfig();

$event->getForm()->add($builder
    ->create('linkedFoo', 'choice', array(
        'choices' => $newChoices,
        'label'   =>'label',
    ))
    ->addModelTransformer(new FooToStringTransformer($em))
);

然后你得到错误:

FormBuilder methods cannot be accessed anymore once the builder is turned
into a FormConfigInterface instance.

然后你尝试像Florian建议的那样,即

$event->getForm()->add('linkedFoo', 'choice', array(
    'choices' => $newChoices,
));
$event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooToStringTransformer($em));

...但是您会收到此错误:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int 
in C:\path\to\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458

这似乎表明第二行(添加 ModelTransformer)永远不会被调用,因为在你到达那里之前调用失败。->add()


答案 1

多亏了sstok(在github上)的想法,我想我现在已经让它工作了。关键是创建自定义的表单类型,然后使用它来添加模型转换器。

创建自定义表单类型:

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';
    }
}

为新类型创建服务定义:

company_project.form.type.foo_shortlist:
    class: Company\ProjectBundle\Form\Type\FooShortlistChoiceType
    tags:
        - { name: form.type, alias: fooShortlist }
    arguments:
        - @doctrine.orm.entity_manager

主窗体的代码现在如下所示:

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'
            ));
        });

        // ...

    }

    // ...
}

关键是此方法允许您将 ModelTransformer 嵌入到自定义字段类型中,以便每当您添加此类型的新实例时,它都会自动为您添加 ModelTransformer,并防止上一个循环“不能在没有变压器的情况下添加字段,也不能在没有字段的情况下添加转换器”


答案 2

你的听众看起来(几乎:))还好。

只需使用PRE_SUBMIT。在这种情况下,将是发送的原始表单数据(数组)。 将强效包含“其他”。$event->getData()$selectedFoo

如果是这种情况,您将在侦听器中使用formFactory将“短”“选择”字段替换为完整的字段。

$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);
});

你问了那么多问题,我不知道从哪里开始。

关于dataTransformers:直到你想将原始数据转换为不同的表示形式(“2013-01-01” ->个新的DateTime(“2013-01-01”)),否则你不需要转换器。


推荐