我正试图与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)
)
;
// ...
}
// ...
}
现在,我想检查提交的值,以便我使用Form Event Listener,如下所示。
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
字段的配置/变换器/标签,除了来改变可用的选择......是否有其他方法可以做到这一点?例如。类似于$form->getField()->updateChoices()
?
提前感谢您提供的任何帮助!
C
P.S。是否有比Symfony网站更好的文档或讨论形式,事件等?例如。 PRE_SET_DATA,PRE_SUBMIT,SUBMIT等之间有什么区别?他们什么时候开除?它们应该用于什么?继承如何使用自定义表单字段?什么是表单和构建器,它们如何交互以及何时处理每个表单?您应该如何,何时以及为何使用可以通过$form->getConfig()->getFormFactory()
访问的FormFactory?等。
编辑:回应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))
);
然后你得到错误:
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()
调用在你到达之前失败了。
答案 0 :(得分:29)
感谢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并阻止前一个循环“无法添加没有变压器的字段如果没有字段“
,则无法添加变压器答案 1 :(得分:1)
你的听众看起来(差不多:))好的。
只需使用PRE_SUBMIT即可。
在这种情况下,$event->getData()
将是发送的原始表单数据(数组)。
$selectedFoo
将有效地包含“其他”。
如果是这种情况,您将通过在侦听器中使用formFactory将“short”'choice'字段替换为完整字段。
$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);
});
你问了很多问题,我不知道从哪里开始。
关于dataTransformers: 直到您想要将原始数据转换为不同的表示形式(“2013-01-01” - > new DateTime(“2013-01-01”)),然后您不需要变换器。
答案 2 :(得分:1)
对于仍在寻找在表单事件中添加/重新添加Model Transformer的更好方法的任何人,我认为最好的解决方案是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函数中删除并重新添加所需的字段,则该字段的模型转换器将不会丢失。
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);
}
}
}
请注意,这是一个简单的示例。我已使用此解决方案轻松处理表单,使其在不同位置具有多个字段状态。