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