Php Symfony2表单事件和模型转换器

Php Symfony2表单事件和模型转换器,php,forms,symfony,doctrine-orm,Php,Forms,Symfony,Doctrine Orm,我在尝试与Symfony2的表单生成器、事件和变形金刚搏斗时陷入了困境。。。希望这里有更有经验的人能帮忙 我有一个表单字段(选择下拉列表),其中包含一些映射到实体的值(短列表)。其中一个选项是“其他”。假设现在没有AJAX,当用户提交表单时,我想检测他们是否选择了“其他”(或短名单中没有的任何其他选项)。如果他们选择了这些选项中的一个,那么应该显示选项的完整列表,否则只显示短名单。应该很容易,对吧?;) 因此,我有我的表单类型,它显示了基本的候选列表。代码如下所示: namespace Comp

我在尝试与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)
            )
        ;

        // ...

    }

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

        // ...

    }

    // ...
}
现在,我想检查提交的值,所以我使用一个表单事件监听器,如下所示

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?
    });
}
但是,它会失败,并显示如下错误消息:

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

        // ...

    }

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

        // ...

    }

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

基本上我不想弄乱
linkedFoo
字段的config/transformers/label,只是想更改可用的选项。。。还有别的办法吗?例如,类似于
$form->getField()->updatechices()

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

C

另外,是否有比Symfony网站更好的表格、活动等文档或讨论?例如,预设置数据、预提交、提交等之间有什么区别?他们什么时候被解雇?它们应该用来做什么?继承如何处理自定义表单字段?什么是表单和构建器,它们是如何交互的,您应该在什么时候处理它们?如何、何时以及为什么使用可以通过
$form->getConfig()->getFormFactory()
访问的FormFactory?等等


编辑:根据Florian的建议,这里有一些关于尝试过但不起作用的东西的更多信息:

如果尝试在事件中获取FormBuilder,如下所示:

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

        // ...

    }

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

        // ...

    }

    // ...
}
然后您会得到错误:

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()
调用在您到达之前失败。

您的侦听器看起来(几乎:)正常

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

如果是这样,您将通过在侦听器中使用formFactory将“short”‘choice’字段替换为完整字段

$builder->addEventListener(FormEvents::PRE_SUBMIT,函数(FormEvent$event){
$data=$event->getData();
if(空($data['linkedFoo'])| |$data['linkedFoo']!=='other'){
返回;
}
//现在我们知道用户选择“其他”
//因此,我们将“linkedFoo”字段改为“fulllist”
$event->getForm()->add('linkedFoo','choice',数组(
'choices'=>fullList,//$em->getRepository('Foo')->getFullList()?
));
$event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(新脚变压器);
});
你问了这么多问题,我不知道从哪里开始

关于数据转换器:
除非您想将原始数据转换为不同的表示形式(“2013-01-01”->新的日期时间(“2013-01-01”)),否则您不需要转换器。

多亏了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(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)
            )
        ;

        // ...

    }

    // ...
}
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 
/** @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))
);
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,并防止前面的循环“不能添加没有转换器的字段,也不能添加没有字段的转换器”

对于仍在寻找更好的方式在表单事件中添加/重新添加模型转换器的任何人,我认为最好的解决方案是从所有积分中选择一个,这是一个出色的解决方案,请转到@total

因此,如果您实现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函数中删除并重新添加所需的字段,那么该字段的模型转换器将不会丢失

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

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

复杂主题:)首先:您使用的是symfony/form的哪个版本?目前正在使用symfony-2.3.6,同时更新2.3.x分支。我必须冲刺,因此还无法彻底了解这一点。但是,我认为我需要使用PRE_SUBMIT,因为我想处理这样的情况,即用户提交表单时,表单中包含了短名单“OTHER”选项(例如,如果AJAX更新失败)。我认为PRE_SET_数据对此不可见。是的,对不起,应该使用PRE_SUBMIT来获取发布的数据。(我的不好,帖子更新了)。好的,刚刚有机会看看这个,你例子中建议的侦听器实际上和我的一样。。。只是我使用$repo->getListAsArray()来返回数组,而不是$fullList。问题在于,在侦听器中使用getForm()->add()会覆盖以前的字段设置并删除