如何避免与 Doctrine2 和 Zend Framework 2 的多对多关系重复?

Posted

技术标签:

【中文标题】如何避免与 Doctrine2 和 Zend Framework 2 的多对多关系重复?【英文标题】:How to avoid duplicates on ManyToMany relation with Doctrine2 and Zend Framework2? 【发布时间】:2014-04-27 12:30:41 【问题描述】:

目标是拥有 2 个实体文章和标签,具有多对多关系,其中标签表中的标签保持唯一,即使为其他文章声明相同的标签。 我尝试用代码更好地解释:

文章实体:

/**
 * @ORM\Entity
 * @ORM\Table(name="articles") 
 */
class Article 

    /**
     * @ORM\Id @ORM\GeneratedValue(strategy="AUTO") @ORM\Column(type="integer")
     * @var int 
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     * @var string
     */
    protected $name;

    /**
     * @ORM\ManyToMany(targetEntity="Tag\Entity\Tag",inversedBy="platforms",cascade="persist","remove")
     * 
     * @return Tag[]
     */
    protected $tags;

    public function __construct() 
        $this->tags = new ArrayCollection();
    

    public function setId($id) 
        $this->id = $id;
    

    public function getId() 
        return $this->id;
    

    public function setName($name) 
        $this->name = $name;
    

    public function getName() 
        return $this->name;
    

    public function setTags($tags)
        $this->tags = $tags;
    

    public function setTagsFromArray(array $tags)
        foreach($tags as $tag)
            $this->tags->add(new Tag($tag));
                
    

    /**
     * Return the associated Tags
     * 
     * @param boolean $getOnlyTagsName [optional] If set to true return a simple array of string (tags name). 
     * If set to false return array of Tag objects. 
     * 
     * @return Tag[]|string[] 
     */
    public function getTags($getOnlyTagsName=false) 
        if ($getOnlyTagsName)
            return array_map(function($i)  return $i->getName(); , $this->tags->toArray());
        

        return $this->tags;
    

    public function addTags($tags) 
        foreach($tags as $tag)
            $this->tags->add($tag);
        
    

    public function removeTags($tags) 
        foreach ($tags as $tag)
            $this->tags->removeElement($tag);        
        
      

标记实体:

/**
 * @ORM\Entity
 * @ORM\Table(name="tags")
 * ORM\HasLifecycleCallbacks // NOT USED
 */
class Tag 

    /**
     * @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue
     * @var int
     */
    protected $id;

    /**
     * @ORM\Column(type="string",unique=true)
     * @var string 
     */
    protected $name;

    /**
     * @ORM\Column(type="datetime",name="created_at")
     * @var datetime 
     */
    protected $createdAt;

    /**
     * @ORM\Column(type="datetime",name="updated_at")
     * @var datetime 
     */
    protected $updatedAt;

    /**
     * @ORM\ManyToMany(targetEntity="Article\Entity\Article", mappedBy="tags")
     * var Tag[] 
     */
    protected $platforms;

    /**
     * Constructor
     *
     * @param string $name Tag's name
     */
    public function __construct($name = null) 
        $this->setName($name);
        $this->setCreatedAt(new DateTime('now'));
        $this->setUpdatedAt(new DateTime('now'));
    

    /**
     * Avoid duplicate entries.
     * 
     * ORM\PrePersist // NOT USED
     */
    public function onPrePersist(LifecycleEventArgs $args) 

    

    /**
     * Avoid duplicate entries.
     * 
     * ORM\PreUpdate  // NOT USED
     */
    public function onPreUpdate(PreUpdateEventArgs $args) 
    

    public function setId($id) 
        $this->id = $id;
    

    /**
     * Returns tag's id
     *
     * @return integer
     */
    public function getId() 
        return $this->id;
    

    /**
     * Sets the tag's name
     *
     * @param string $name Name to set
     */
    public function setName($name) 
        $this->name = $name;
    

    /**
     * Returns tag's name
     *
     * @return string
     */
    public function getName() 
        return $this->name;
    

    public function setCreatedAt(DateTime $date) 
        $this->createdAt = $date;
    

    public function getCreatedAt() 
        return $this->createdAt;
    

    public function setUpdatedAt(DateTime $date) 
        $this->updatedAt = $date;
    

    public function getUpdatedAt() 
        return $this->updatedAt;
        

然后我有文章表单(带有文章字段集),其中有一个 tagsinput(jquery 插件)元素。所以表单发布是这样的:

object(Zend\Stdlib\Parameters)[151]
  public 'security' => string 'dc2a6ff900fbc87933e07bd35ef36709...' (length=65)
  public 'article' => 
    array (size=3)
      'id' => string '' (length=0)
      'name' => string 'Article 1' (length=13)
      'tags' => 
        array (size=3)
          0 => string 'tag1' (length=3)
          1 => string 'tag2' (length=4)
          2 => string 'tag3' (length=7)
  public 'submit' => string 'Add' (length=8)

第一次插入一切顺利,但是当我尝试使用 article1 标记之一插入另一篇文章时,我收到错误:

An exception occurred while executing 'INSERT INTO tags (name, created_at, updated_at) VALUES (?, ?, ?)' with params ["tag2", "2014-04-26 22:05:37", "2014-04-26 22:05:37"]:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'tag2' for key 'UNIQ_6FBC94265E237E06'

我知道我可以使用 prePersist 和 preUpdate 事件监听器,但是我做了一些测试,但我不知道如何使用 Unit Of Work 来避免重复。

【问题讨论】:

【参考方案1】:

您不能将标签name属性(和数据库字段)定义为unique。每次您使用先前选择的标签名称设置新标签时,您都会遇到违反完整性约束。您只需要控制不为与同一篇文章相关的标签选择名称。

您可以将 Zend\Validator\Db\NoRecordExists 验证器添加到 tagsinput 元素来实现此目的。应该是这样的:

$tagsinput->getValidatorChain()->addValidator(
    new NoRecordExists( array(
    'adapter' => $this->getServiceManager()->get( 'Zend\Db\Adapter\Adapter' ),
    'table' => 'tag',
    'field' => 'name',
    'message' => 'The tag name already exists', 
    'exclude' => 'article_id = ' . $article->getId(),
) );

--

编辑: 如果有多个标签,则此解决方案将不起作用,此时 tagsinput 的值将是一个数组。您可以遍历数组来验证每个标签,但这不是一个有效的解决方案,它会执行 N 个查询。您可以实现一个类似的验证器,它接受像 TAG_NAME IN ('tag1', 'tag2', 'tag3')

这样的 where 子句

--

请注意,此验证器不适用于 Doctrine2; tablefieldexclude 参数具有数据库值。

向现有文章添加标签时应该可以使用。要验证新文章没有重复标签,您可以在客户端进行,也可以write your own validator。

【讨论】:

谢谢,但在一些帮助下,我通过更改 setTagsFromArray 函数来检查标签是否已存在找到了解决方案。

以上是关于如何避免与 Doctrine2 和 Zend Framework 2 的多对多关系重复?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将 Doctrine 2 与 Zend Framework 1.1x 集成?

Doctrine2更新导致Zend Framework 3中的AnnotationRegistry registerLoader错误

zend 框架 2 + 教义 2 安装

Zend Framework 2 + Doctrine 2 [关闭]

在 Zend 框架 2 中使用 MappedSuperclass 的 Doctrine 2 多对多

Zend Framework 和 Doctrine 2 - 我的单元测试是不是足够?