Symfony2 表单事件和模型转换器 [英] Symfony2 form events and model transformers

查看:25
本文介绍了Symfony2 表单事件和模型转换器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在努力与 Symfony2 的表单构建器、事件和转换器搏斗时陷入困境……希望这里有人更有经验并能提供帮助!

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!

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

I have a form field (select drop-down) which contains some values (a shortlist) which maps to an Entity. One of these options is "other". Assume there's no AJAX for now and when a user submits the form I want to detect if they chose 'other' (or any other option not in the shortlist). If they chose one of these options then the full list of options should be shown, otherwise just show the shortlist. Should be easy, right? ;)

所以,我有我的表单类型,它可以很好地显示基本的候选名单.代码如下所示:

So, I have my Form Type and it displays the basic shortlist just fine. The code looks something like this:

namespace CompanyProjectBundleFormType;

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

        // ...

    }

    // ...
}

现在,我想检查提交的值,因此我使用了表单事件侦听器,如下所示.

Now, I want to check the value submitted so I use a Form Event Listener as follows.

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

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

However, it fails with an error message like this:

Notice: Object of class Proxies\__CG__CompanyProjectBundleEntityFoo could not be converted to int in path	oprojectsymfonysymfonysrcSymfonyComponentFormExtensionCoreChoiceListChoiceList.php line 458 

我认为这个错误是因为当 linkedFoo 被覆盖时它删除了 modelTransformer?我尝试了在事件关闭时访问构建器的各种方法,但这似乎不起作用(返回值出乎意料).除了 $event->getForm()->add() 之外,我还应该在事件中使用其他方法吗?或者我的方法是否存在更根本的问题?

I presume this error is because when the linkedFoo was over-written it removed the modelTransformer? I tried various ways of accessing a builder in the event's closure but this didn't seem to work (the return values were unexpected). Is there some other method I should be using in the event other than $event->getForm()->add()? Or is there a more fundamental problem with my approach here?

基本上我不想弄乱 linkedFoo 字段的 config/transformers/labels except 来更改可用的选项......还有其他方法吗那?例如.类似于 $form->getField()->updateChoices()?

Basically I don't want to mess with the linkedFoo field's config/transformers/labels except to change the choices available... is there some other way to do that? E.g. something like $form->getField()->updateChoices()?

预先感谢您提供的任何帮助!

Thanks in advance for any help you can offer!

C

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

P.S. is there any better documentation or discussion of the forms, events, etc than on the Symfony website? E.g. what's the difference between PRE_SET_DATA, PRE_SUBMIT, SUBMIT, etc? When are they fired? What should they be used for? How does inheritance work with custom form fields? What is a Form and a Builder, how do they interact and when should you deal with each? How, when and why should you use the FormFactory you can access through $form->getConfig()->getFormFactory()? Etc..

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

In response to Florian's suggestion here's some more info about things that were tried but do not work:

如果您尝试像这样在事件中获取 FormBuilder:

If you try to get the FormBuilder within the event like this:

/** @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.

然后你尝试像弗洛里安建议的那样,即

So then you try something like Florian suggested, i.e.

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

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

...but you get this error instead:

Notice: Object of class Proxies\__CG__CompanyProjectBundleEntityFoo could not be converted to int 
in C:path	ovendorsymfonysymfonysrcSymfonyComponentFormExtensionCoreChoiceListChoiceList.php line 458

这似乎表明第二行(添加 ModelTransformer)从未被调用,因为 ->add() 调用在您到达那里之前失败了.

Which seems to suggest that the second line (which adds the ModelTransformer) is never called because the ->add() call is failing before you can get there.

推荐答案

感谢 sstok(在 github 上)的想法,我想我现在已经可以工作了.关键是创建一个自定义的 Form Type,然后使用它来添加 ModelTransformer.

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.

创建自定义表单类型:

namespace CaponicaMagnetBundleFormType;

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: CompanyProjectBundleFormTypeFooShortlistChoiceType
    tags:
        - { name: form.type, alias: fooShortlist }
    arguments:
        - @doctrine.orm.entity_manager

主窗体的代码现在看起来像这样:

The main form's code now looks something like this:

namespace CompanyProjectBundleFormType;

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 并防止之前的无法添加"循环一个没有转换器的字段并且不能添加一个没有字段的转换器"

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"

这篇关于Symfony2 表单事件和模型转换器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆