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

时间:2014-03-26 19:33:26

标签: php symfony doctrine-orm

我正在Symfony的文章编辑器上工作,内置标记功能:

You can add tags from inside the article form.

控制器

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()
        ));

    }
}

表格

标记表单注册为具有@Doctrine参数的服务,因此我可以在类中使用实体管理器。标签表单嵌入在文章表单中。

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' => 'Acme\Bundle\Entity\Article',
            '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(\Doctrine\Bundle\DoctrineBundle\Registry $doctrine) {
        $this->entityManager = $doctrine->getManager();
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\Bundle\Entity\Tag'
        ));
    }

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

数据转换器

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

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;
    }
}

当我尝试使用已存在的标记保存文章时,reverseTransform()函数成功返回原始标记对象,但是 DBAL通过其__toString()将对象转换回字符串方法,并且Doctrine仍会启动INSERT查询而不是UPDATE,因此我收到了下一个错误:

  

执行' INSERT INTO Tag(名称)时发生异常   价值观(?)'用params [{}]:

     

SQLSTATE [23000]:完整性约束违规:1062重复条目   '现有标签'关键字' UNIQ_0123456789ABCDE'

我该如何解决这个问题?当我输入已经在使用的标签名称时,我希望Symfony在文章标签关系中使用相同的标签。实体类出现在我之前关于how to avoid duplicate entries in a many-to-many relationship with Doctrine的问题中。

1 个答案:

答案 0 :(得分:1)

你的变压器有一个错误。而不是检查name是否为null,您应该验证是否返回了标记:

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

您也不需要保留标记,因为默认情况下,doctrine会级联持久化所有相关实体。

完整方法:

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;
}