Symfony2形式事件和模型变压器 [英] Symfony2 form events and model transformers

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

问题描述



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



所以,我有我的表单类型,它显示基本的候选列表很好。代码看起来像这样:

 命名空间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)

;

// ...

}

// ...
}

现在,我想检查提交的值,所以我使用一个Form Event Listener如下。

  public function buildForm(FormBuilderInterface $ builder,array $ options){
// ...这段代码刚刚之后的代码片段

$ 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 - 需要变压器?
});
}

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

 注意:Proxies类的对象\__CG__\Company\ProjectBundle\Entity\Foo不能转换为\path中的int \to\project\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

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



基本上我不想混淆 linkedFoo field的config / transformers / labels 除了以更改可用的选项...还有其他一些方法吗?例如。像 $ form-> getField() - > updateChoices()



提前感谢帮助您提供!



C



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






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



如果您尝试在此事件中获取FormBuilder:

  / ** @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))
);

然后你会收到错误:

 一旦构建器将
转换为FormConfigInterface实例,FormBuilder方法将无法访问。

所以你尝试像Florian建议的一样,即

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

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

 注意:Proxies的类型\__CG__\Company\ProjectBundle\Entity\Foo的对象无法在C:\path中转换为int 
\to\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458

这似乎表明第二行(添加了ModelTransformer)从来没有被调用,因为 - > add() 调用失败,然后才能到达。

解决方案

感谢sstok(github) ),我想我已经工作了。关键是创建一个自定义的表单类型,然后使用它来添加ModelTransformer。



创建自定义表单类型:

 命名空间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';
}
}

为新的类型创建服务定义: p>

  company_project.form.type.foo_shortlist:
class:Company\ProjectBundle\Form\Type\FooShortlistChoiceType
标签:
- {name:form.type,别名:fooShortlist}
参数:
- @ doctrine.orm.entity_manager
/ pre>

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

  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'=&标签'
));
});

// ...

}

// ...
}

关键是该方法允许您将自定义字段类型中的ModelTransformer嵌入,以便每当您自动添加此类型的新实例时为您添加了ModelTransformer,并阻止以前的不能添加没有变压器的字段的循环,并且无法添加没有字段的变压器


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!

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

        // ...

    }

    // ...
}

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__\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 

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?

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

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..


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

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

Then you get the error:

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__\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

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.

解决方案

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"

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

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