如何在 Symfony 中设置数据转换器以重用现有实体?

2022-01-03 00:00:00 php symfony doctrine-orm

我正在 Symfony 中开发一个带有内置标记功能的文章编辑器:

控制器

class MainController 扩展控制器{公共函数 indexAction(Request $request, $id){$em = $this->getDoctrine()->getManager();//$article = ...$form = $this->createForm(new ArticleType(), $article);$form->handleRequest($request);如果 ($form->isValid()) {$em->persist($article);$em->flush();返回 $this->redirect($this->generateUrl('acme_edit_success'));}return $this->render('AcmeBundle:Main:index.html.twig', array('形式' =>$form->createView()));}}

表格

标签表单被注册为带有 @Doctrine 参数的服务,所以我可以在类中使用实体管理器.标签表单嵌入在文章表单中.

文章类型.php

class ArticleType 扩展 AbstractType{公共函数 buildForm(FormBuilderInterface $builder, array $options){$builder->添加('内容')->add('tags', 'collection', array('类型' =>'acme_bundle_tagtype','allow_add' =>真的,'allow_delete' =>真的,'by_reference' =>错误的))->add('保存', '提交');}公共函数 setDefaultOptions(OptionsResolverInterface $resolver){$resolver->setDefaults(array('data_class' =>'AcmeBundleEntityArticle','cascade_validation' =>真的));}公共函数 getName(){返回acme_bundle_articletype";}}

TagType.php

class TagType 扩展 AbstractType{私人 $entityManager;公共函数 buildForm(FormBuilderInterface $builder, array $options){$transformer = new TagTransformer($this->entityManager);$builder->add($builder->create('name')->addModelTransformer($transformer));}函数 __construct(DoctrineBundleDoctrineBundleRegistry $doctrine) {$this->entityManager = $doctrine->getManager();}公共函数 setDefaultOptions(OptionsResolverInterface $resolver){$resolver->setDefaults(array('data_class' =>'AcmeBundleEntityTag'));}公共函数 getName(){返回acme_bundle_tagtype";}}

数据转换器

我创建了这个数据转换器来检查给定的标签是否已经存在,然后将标签对象转换为数据库中已经存在的对象:

class TagTransformer 实现 DataTransformerInterface{/*** @var 对象管理器*/私人 $om;/*** @param ObjectManager $om*/公共函数 __construct(ObjectManager $om){$this->om = $om;}公共函数转换($tag){if (null === $tag) {返回 '​​';}返回 $tag;}公共函数 reverseTransform($name){如果(!$名称)返回空;$tag = $this->om->getRepository('AcmeBundle:Tag')->findOneByName($name);如果(!$标签){$tag = 新标签();$tag->setName($name);}返回 $tag;}}

当我尝试使用已经存在的标签保存文章时,reverseTransform() 函数成功返回原始标签对象,但 DBAL 通过其将对象转换回字符串__toString() 方法,Doctrine 仍然启动一个 INSERT 查询而不是 UPDATE,所以我得到下一个错误:<块引用>

执行'INSERT INTO Tag (name) 时发生异常VALUES (?)' 带参数 [{}]:

SQLSTATE[23000]:违反完整性约束:1062 重复条目键UNIQ_0123456789ABCDE"的现有标签"

我该如何解决这个问题?当我输入一个已经在使用的标签名称时,我希望 Symfony 在文章-标签关系中使用相同的标签.实体类出现在我之前关于如何避免与 Doctrine 的多对多关系中的重复条目 的问题中.

解决方案

你的转换器有一个错误.您应该验证是否返回了标签,而不是检查 name 是否为 null:

 if (!$tag) {$tag = 新标签();$tag->setName($name);}

你也不需要持久化标签,因为默认情况下学说会级联持久化所有相关实体.

完整方法:

public function reverseTransform($name){如果(!$名称){返回空;}$tag = $this->om->getRepository('AcmeBundle:Tag')->findOneByName($name);如果(!$标签){$tag = 新标签();$tag->setName($name);}返回 $tag;}

I'm working on an article editor in Symfony with built-in tagging capability:

The controller

class MainController extends Controller
{
    public function indexAction(Request $request, $id)
    {
        $em = $this->getDoctrine()->getManager();

        // $article = ...

        $form = $this->createForm(new ArticleType(), $article);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $em->persist($article);
            $em->flush();
            return $this->redirect($this->generateUrl('acme_edit_success'));
        }

        return $this->render('AcmeBundle:Main:index.html.twig', array(
            'form' => $form->createView()
        ));

    }
}

The forms

The tag form is registered as a service with the @Doctrine argument, so I can use the entity manager inside the class. The tag form is embed inside the article form.

ArticleType.php

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('content')
            ->add('tags', 'collection', array(
                'type' => 'acme_bundle_tagtype',
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false
            ))
            ->add('save', 'submit')
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AcmeBundleEntityArticle',
            'cascade_validation' => true
        ));
    }

    public function getName()
    {
        return 'acme_bundle_articletype';
    }
}

TagType.php

class TagType extends AbstractType
{
    private $entityManager;

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new TagTransformer($this->entityManager);

        $builder->add(
            $builder->create('name')
                ->addModelTransformer($transformer)
        );
    }

    function __construct(DoctrineBundleDoctrineBundleRegistry $doctrine) {
        $this->entityManager = $doctrine->getManager();
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AcmeBundleEntityTag'
        ));
    }

    public function getName()
    {
        return 'acme_bundle_tagtype';
    }
}

The data transformer

I created this data transformer to check if the given tag already exists and then transform the tag object to the one that already exists in the database:

class TagTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $om;

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

    public function transform($tag)
    {
        if (null === $tag) {
            return '';
        }

        return $tag;
    }

    public function reverseTransform($name)
    {
        if (!$name)
            return null;

        $tag = $this->om
            ->getRepository('AcmeBundle:Tag')
            ->findOneByName($name)
        ;

        if (!$tag) {
            $tag = new Tag();
            $tag->setName($name);
        }

        return $tag;
    }
}

When I try to save an article with an already existing tag, the reverseTransform() function successfully returns the original tag objects, but the DBAL converts the object back to a string by its __toString() method, and Doctrine still initiates an INSERT query instead of UPDATE, so I get the next error:

An exception occurred while executing 'INSERT INTO Tag (name) VALUES (?)' with params [{}]:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'An Existing Tag' for key 'UNIQ_0123456789ABCDE'

How can I fix this? When I enter a tag name that's already in use, I want Symfony to use the same tag in the article-tag relationship. The entity classes appear in my previous question about how to avoid duplicate entries in a many-to-many relationship with Doctrine.

解决方案

There's one mistake in your transformer. Instead of checking if name is null you should verify if a tag was returned:

    if (!$tag) {
        $tag = new Tag();
        $tag->setName($name);
    }

You also don't need to persist the tag since by default doctrine will cascade persist all the related entities.

Full method:

public function reverseTransform($name)
{
    if (!$name) {
        return null;
    }

    $tag = $this->om
        ->getRepository('AcmeBundle:Tag')
        ->findOneByName($name)
    ;

    if (!$tag) {
        $tag = new Tag();
        $tag->setName($name);
    }

    return $tag;
}

相关文章