Symfony2 和 Selectize.js:在实体字段类型中保留新项目的最清晰方法?

Symfony2 and Selectize.js: Clearest way to persist new items in entity field type?

在 Symfony2 中,我有 BandType,我在其中添加实体 Tag:

->add('tags', 'entity', [
     'label' => 'Tags',
     'class' => 'DbBundle:Tag',
     'property' => 'title',
     'multiple'  =>  true,
])

这会生成多个 select 元素,我可以在其中从数据库 (Doctrine) 中选择 现有标签 。但是我需要动态添加新标签,这些标签还不存在。

在客户端,我使用 jQuery 插件 Selectize.js,它允许我向 select 框添加新标签。但是在提交表单后,新的 标签没有保存 .

所以我的问题是 - 从 select 框(实体字段类型) 中保存新项目的最清晰方法是什么?

为您的实体使用 Data Transformer。在 reverseTransform 方法中,如果您没有找到新添加的 band,只需在那里创建它而不是抛出 TransformationFailedException。

如另一个答案所述,您需要为您的实体使用 Data Transformer,如果找不到用户要求的实体,则 return 一个新实体.

有多种方法可以做到这一点。这是一种方法,从恰好使用 selectize.js 的应用程序简化而来,但这些概念适用于您可能在前端拥有的任何 UI。

class SubjectTransformer implements DataTransformerInterface
{
    protected $em;

    public function __construct($em)
    {
        $this->em = $em;
    }

    //public function transform($val) { ... }

    public function reverseTransform($str)
    {
        $repo = $this->em->getRepository('AppBundle:Subject');

        $subject = $repo->findOneByName($str);
        if($subject)
            return $subject;

        //Didn't find it, so it must be new 
        $subject = new Subject;
        $subject->setName($str);
        $this->em->persist($subject);

        return $subject;
    }
}

具体来说,这个 DataTransformer 用于 CollectionType 字段的 entry_type

  • 在其构造函数中采用实体管理器
  • reverseTransform 中,使用 EM 从数据库中检索值
  • 如果找不到,它会创建一个新实体,并保留它
  • 明确地不会刷新实体,以防您的表单processor/controller想要在实际提交之前对新实体执行额外验证

其他可能的变化包括不调用 em->persist;呼叫 em->flush;或者(可能理想情况下)传递服务来管理 search/creation,而不是直接使用实体管理器。 (这样的服务可能会实施近似重复检测、不良语言过滤、只允许某些用户创建新标签等)

一种可能的解决方案是使用 FormEvents。这是示例代码:

namespace AppBundle\Form;

use AppBundle\Entity\Tag;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PostType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $manager;

    /**
     * Constructor
     *
     * @param ObjectManager $manager
     */
    public function __construct(ObjectManager $manager)
    {
        $this->manager = $manager;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('content')
            ->add('tags')
        ;
        $builder->get('tags')->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) {
                $choiceList = $event->getForm()->getConfig()->getAttribute('choice_list');
                $array = is_null($event->getData()) ? [] : $event->getData();
                $choices = $choiceList->getChoicesForValues($array);

                if (count($choices) !== count($array)) {
                    $values = $choiceList->getValuesForChoices($choices);
                    $diff = array_merge(array_diff($values, $array), array_diff($array, $values));

                    foreach ($diff as $value) {
                        $new = new Tag($value);
                        $this->manager->persist($new);
                        $this->manager->flush();
                        $values[] = $new->getId();
                    }

                    $event->setData($values);
                }
            }
        );
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Post'
        ));
    }
}