【问题标题】:Symfony2 form events and model transformersSymfony2 表单事件和模型转换器
【发布时间】:2013-10-23 07:18:15
【问题描述】:

我在试图与 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 

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

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

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

C

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


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

如果您尝试像这样在事件中获取 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))
);

然后你得到错误:

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

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

$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() 调用失败了。

【问题讨论】:

  • 确实很复杂 :) 首先:你使用的是什么版本的 symfony/form?
  • 目前使用 Symfony-2.3.6,边走边更新 2.3.x 分支。

标签: php forms symfony doctrine-orm


【解决方案1】:

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

创建自定义表单类型:

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

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

关于数据转换器: 直到您想将原始数据转换为不同的表示形式(“2013-01-01”-> new DateTime("2013-01-01")),那么您不需要转换器。

【讨论】:

  • 我必须冲刺,所以还不能彻底看这个。但是,我认为我需要使用 PRE_SUBMIT,因为我想处理用户提交带有候选列表“OTHER”选项的表单的情况(例如,如果 AJAX 更新失败)。我不认为 PRE_SET_DATA 对此有可见性。
  • 如何,是的,抱歉,应该使用 PRE_SUBMIT 来获取发布的数据。 (我的错,帖子已更新)。
  • 好的,刚刚有机会看看这个,你的例子中建议的监听器实际上和我的一样......只是我使用 $repo->getListAsArray() 返回一个数组而不是 $fullList。问题是在监听器中使用 getForm()->add() 会覆盖之前的字段设置,并移除最初添加的 modelTransformer。那么我们如何将modelTransformer添加回来呢?
  • 通过实验更新答案:使用$form->getConfig()->addModelTransformer();
  • 对不起,这也不能让我们到达那里:-/我已经尝试过类似的东西。如果您尝试此操作,我将更新我的 OP 并提供更多详细信息。
【解决方案3】:

对于仍在寻找在表单事件中添加/重新添加模型转换器的更好方法的任何人,我认为最好的解决方案是来自 this post 的解决方案,所有积分都归于 @Toilal 以获得这个出色的解决方案

因此,如果您实现 ModelTransformerExtension 并将其定义为服务,并更改一些代码,例如,从

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

类似于:

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),
            ]
        )
        ;
}

现在,如果我们在 eventlistener 函数中删除并重新添加所需的字段,该字段的 Model Transformer 将不会丢失。

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

请注意,这是一个简单的示例。我已使用此解决方案轻松处理表单以在不同位置具有多个字段状态。

【讨论】:

    【解决方案4】:

    使用匿名函数作为事件处理程序从父作用域继承 $builder 也可以。

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) use ($builder) {
        $data = $event->getData();
    
        if (empty($data['linkedFoo'])) {
           return;
        }
        $builder->add('linkedFoo', 'choice', [
            'choices' => $this->fooRepo->getListAsArray(
                $data->getLinkedfoo()->getId()
             ),
        ]);
        $builder->get('linkedFoo')
            ->addModelTransformer(
                new FooTransformer()
            );
    
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 2012-03-27
      • 1970-01-01
      • 2014-09-30
      相关资源
      最近更新 更多