扩展 EntityType 以允许使用 AJAX 调用设置额外的选择
Extending EntityType to allow extra choices set with AJAX calls
我尝试创建一个 Symfony 自定义类型扩展核心“entity”类型。
但我想将它与 Select2 版本 4.0.0 一起使用(ajax 现在可以与 "select" html 元素一起使用,而不是与像以前一样隐藏 "input")。
- 此类型应创建一个空 select,而不是扩展 "entity" 类型的完整实体列表。
这通过设置选项(见 configureOption)来工作:
'choices'=>array()
- 通过编辑附加到表单的对象,它应该用对象的当前数据填充 select。我解决了这个问题,但只是为了使用以下 buildView 方法查看...
Select2 识别 html "select" 的内容,并与 ajax 一起工作。
但是当表单回发时,Symfony 无法识别 selected 选项,(因为不允许?)
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "user": The choice "28" does not exist or is not unique
我尝试了几种使用 EventListeners 或 Subscribers 的方法,但找不到有效的配置。
使用 Select2 3.5.* 我解决了表单事件和覆盖隐藏表单类型的问题,但这里扩展实体类型要困难得多。
如何构建我的类型以让它管理我的实体的反向转换?
自定义类型:
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
class AjaxEntityType extends AbstractType
{
protected $router;
public function __construct($router)
{
$this->router = $router;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setAttribute('attr',array_merge($options['attr'],array('class'=>'select2','data-ajax--url'=>$this->router->generate($options['route']))));
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = $form->getConfig()->getAttribute('attr');
$choices = array();
$data=$form->getData();
if($data instanceOf \Doctrine\ORM\PersistentCollection){$data = $data->toArray();}
$values='';
if($data != null){
if(is_array($data)){
foreach($data as $entity){
$choices[] = new ChoiceView($entity->getAjaxName(),$entity->getId(),$entity,array('selected'=>true));
}
}
else{
$choices[] = new ChoiceView($data->getAjaxName(),$data->getId(),$data,array('selected'=>true));
}
}
$view->vars['choices']=$choices;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array('route'));
$resolver->setDefaults(array('choices'=>array(),'choices_as_value'=>true));
}
public function getParent() {
return 'entity';
}
public function getName() {
return 'ajax_entity';
}
}
家长形式
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AlarmsType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text',array('required'=>false))
->add('user','ajax_entity',array("class"=>"AppBundle:Users","route"=>"ajax_users"))
->add('submit','submit');
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'AppBundle\Entity\Alarms','validation_groups'=>array('Default','form_user')));
}
/**
* @return string
*/
public function getName()
{
return 'alarms';
}
}
问题已解决。
解决方案是在 PRE_SET_DATA 和 PRE_SUBMIT FormEvents 中使用 'choices'=>$selectedChoices 重新创建表单字段。
可以使用 $event->getData()
从事件中检索选定的选项
看看我创建的包,它实现了这个方法:
这是我的方法,基于您的捆绑包,仅适用于一种表单类型中的实体类型。
用法是
MyType extends ExtensibleEntityType
(不要忘记父调用构建表单和配置选项)
和 class 本身
abstract class ExtensibleEntityType extends AbstractType
{
/**
* @var EntityManagerInterface
*/
private EntityManagerInterface $entityManager;
/**
* ExtensibleEntityType constructor.
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function getParent()
{
return EntityType::class;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
$builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'preSubmit'], 50);
}
/**
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$parent = $event->getForm()->getParent();
$options = $form->getConfig()->getOptions();
if (!$options['pre_set_called']) {
$options['pre_set_called'] = true;
$options['choices'] = $this->getChoices($options, $event->getData());
$parent->add($form->getName(), get_class($this), $options);
}
}
/**
* @param FormEvent $event
*/
public function preSubmit(FormEvent $event)
{
$form = $event->getForm();
$parent = $event->getForm()->getParent();
$options = $form->getConfig()->getOptions();
if (!$options['pre_submit_called']) {
$options['pre_submit_called'] = true;
$options['choices'] = $this->getChoices($options, $event->getData());
$parent->add($form->getName(), get_class($this), $options);
$newForm = $parent->get($form->getName());
$newForm->submit($event->getData());
}
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'multiple' => true,
'expanded' => true,
'choices' => [],
'required' => false,
'pre_set_called' => false,
'pre_submit_called' => false,
'validation_groups' => false,
]);
}
/**
* @param array $options
* @param $data
* @return mixed
*/
public function getChoices(array $options, $data)
{
if ($data instanceof PersistentCollection) {
return $data->toArray();
}
return $this->entityManager->getRepository($options['class'])->findBy(['id' => $data]);
}
}
这是我的工作代码,它向与标签 (TagType) 相关的用户 (EntityType) 添加了填充来自 AJAX 调用 (jQuery Select2) 的选项的能力。
class TagType extends AbstractType
{
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$modifyForm = function ($form, $users) {
$form->add('users', EntityType::class, [
'class' => User::class,
'multiple' => true,
'expanded' => false,
'choices' => $users,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($modifyForm) {
$modifyForm($event->getForm(), $event->getData()->getUsers());
}
);
$userRepo = $this->userRepo; // constructor injection
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($modifyForm, $userRepo) {
$userIds = $event->getData()['users'] ?? null;
$users = $userIds ? $userRepo->createQueryBuilder('user')
->where('user.id IN (:userIds)')->setParameter('userIds', $userIds)
->getQuery()->getResult() : [];
$modifyForm($event->getForm(), $users);
}
);
}
//...
}
我尝试创建一个 Symfony 自定义类型扩展核心“entity”类型。
但我想将它与 Select2 版本 4.0.0 一起使用(ajax 现在可以与 "select" html 元素一起使用,而不是与像以前一样隐藏 "input")。
- 此类型应创建一个空 select,而不是扩展 "entity" 类型的完整实体列表。
这通过设置选项(见 configureOption)来工作:
'choices'=>array()
- 通过编辑附加到表单的对象,它应该用对象的当前数据填充 select。我解决了这个问题,但只是为了使用以下 buildView 方法查看...
Select2 识别 html "select" 的内容,并与 ajax 一起工作。 但是当表单回发时,Symfony 无法识别 selected 选项,(因为不允许?)
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "user": The choice "28" does not exist or is not unique
我尝试了几种使用 EventListeners 或 Subscribers 的方法,但找不到有效的配置。
使用 Select2 3.5.* 我解决了表单事件和覆盖隐藏表单类型的问题,但这里扩展实体类型要困难得多。
如何构建我的类型以让它管理我的实体的反向转换?
自定义类型:
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
class AjaxEntityType extends AbstractType
{
protected $router;
public function __construct($router)
{
$this->router = $router;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setAttribute('attr',array_merge($options['attr'],array('class'=>'select2','data-ajax--url'=>$this->router->generate($options['route']))));
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = $form->getConfig()->getAttribute('attr');
$choices = array();
$data=$form->getData();
if($data instanceOf \Doctrine\ORM\PersistentCollection){$data = $data->toArray();}
$values='';
if($data != null){
if(is_array($data)){
foreach($data as $entity){
$choices[] = new ChoiceView($entity->getAjaxName(),$entity->getId(),$entity,array('selected'=>true));
}
}
else{
$choices[] = new ChoiceView($data->getAjaxName(),$data->getId(),$data,array('selected'=>true));
}
}
$view->vars['choices']=$choices;
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array('route'));
$resolver->setDefaults(array('choices'=>array(),'choices_as_value'=>true));
}
public function getParent() {
return 'entity';
}
public function getName() {
return 'ajax_entity';
}
}
家长形式
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AlarmsType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text',array('required'=>false))
->add('user','ajax_entity',array("class"=>"AppBundle:Users","route"=>"ajax_users"))
->add('submit','submit');
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'AppBundle\Entity\Alarms','validation_groups'=>array('Default','form_user')));
}
/**
* @return string
*/
public function getName()
{
return 'alarms';
}
}
问题已解决。
解决方案是在 PRE_SET_DATA 和 PRE_SUBMIT FormEvents 中使用 'choices'=>$selectedChoices 重新创建表单字段。
可以使用 $event->getData()
从事件中检索选定的选项看看我创建的包,它实现了这个方法:
这是我的方法,基于您的捆绑包,仅适用于一种表单类型中的实体类型。 用法是
MyType extends ExtensibleEntityType
(不要忘记父调用构建表单和配置选项)
和 class 本身
abstract class ExtensibleEntityType extends AbstractType
{
/**
* @var EntityManagerInterface
*/
private EntityManagerInterface $entityManager;
/**
* ExtensibleEntityType constructor.
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function getParent()
{
return EntityType::class;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
$builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'preSubmit'], 50);
}
/**
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$form = $event->getForm();
$parent = $event->getForm()->getParent();
$options = $form->getConfig()->getOptions();
if (!$options['pre_set_called']) {
$options['pre_set_called'] = true;
$options['choices'] = $this->getChoices($options, $event->getData());
$parent->add($form->getName(), get_class($this), $options);
}
}
/**
* @param FormEvent $event
*/
public function preSubmit(FormEvent $event)
{
$form = $event->getForm();
$parent = $event->getForm()->getParent();
$options = $form->getConfig()->getOptions();
if (!$options['pre_submit_called']) {
$options['pre_submit_called'] = true;
$options['choices'] = $this->getChoices($options, $event->getData());
$parent->add($form->getName(), get_class($this), $options);
$newForm = $parent->get($form->getName());
$newForm->submit($event->getData());
}
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'multiple' => true,
'expanded' => true,
'choices' => [],
'required' => false,
'pre_set_called' => false,
'pre_submit_called' => false,
'validation_groups' => false,
]);
}
/**
* @param array $options
* @param $data
* @return mixed
*/
public function getChoices(array $options, $data)
{
if ($data instanceof PersistentCollection) {
return $data->toArray();
}
return $this->entityManager->getRepository($options['class'])->findBy(['id' => $data]);
}
}
这是我的工作代码,它向与标签 (TagType) 相关的用户 (EntityType) 添加了填充来自 AJAX 调用 (jQuery Select2) 的选项的能力。
class TagType extends AbstractType
{
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$modifyForm = function ($form, $users) {
$form->add('users', EntityType::class, [
'class' => User::class,
'multiple' => true,
'expanded' => false,
'choices' => $users,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($modifyForm) {
$modifyForm($event->getForm(), $event->getData()->getUsers());
}
);
$userRepo = $this->userRepo; // constructor injection
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($modifyForm, $userRepo) {
$userIds = $event->getData()['users'] ?? null;
$users = $userIds ? $userRepo->createQueryBuilder('user')
->where('user.id IN (:userIds)')->setParameter('userIds', $userIds)
->getQuery()->getResult() : [];
$modifyForm($event->getForm(), $users);
}
);
}
//...
}