php - 如何避免与 Doctrine 的多对多关系中的重复条目?

标签 php symfony doctrine-orm

我正在使用 embed Symfony form直接从文章编辑器添加和删除 Tag 实体。 文章owning side关于协会:

class Article
{
    /**
     * @ManyToMany(targetEntity="Tags", inversedBy="articles", cascade={"persist"})
     */
    private $tags;

    public function addTag(Tag $tags)
    {
        if (!$this->tags->contains($tags)) // It is always true.
            $this->tags[] = $tags;
    }
}

条件在这里没有帮助,因为它始终为真,如果不是,则根本不会将新标签持久保存到数据库中。这是 Tag 实体:

class Tag
{
    /**
     * @Column(unique=true)
     */
    private $name

    /**
     * @ManyToMany(targetEntity="Articles", mappedBy="tags")
     */
    private $articles;

    public function addArticle(Article $articles)
    {
        $this->articles[] = $articles;
    }
}

我已将 $name 设置为唯一,因为每次在表单中输入相同的名称时我都想使用相同的标签。但它不是这样工作的,我得到了异常(exception):

Integrity constraint violation: 1062 Duplicate entry

我需要更改什么才能使用 article_tag,提交标签名称时的默认连接表,它已经在 Tag 表中?

最佳答案

几个月来我一直在与类似的问题作斗争,最终找到了一个似乎在我的应用程序中运行良好的解决方案。这是一个复杂的应用程序,有很多多对多关联,我需要以最高效率处理它们。

此处部分解释了解决方案:http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/faq.html#why-do-i-get-exceptions-about-unique-constraint-failures-during-em-flush

您的代码已经完成了一半:

public function addTag(Tag $tags)
{
    if (!$this->tags->contains($tags)) // It is always true.
        $this->tags[] = $tags;
}

基本上我添加的是在关系的拥有方设置 indexedBy="name"fetch="EXTRA_LAZY",在你的情况下是 Article 实体(您可能需要水平滚动代码块才能看到添加):

class Article
{
    /**
     * @ManyToMany(targetEntity="Tags", inversedBy="articles", cascade={"persist"}, indexedBy="name" fetch="EXTRA_LAZY")
     */
    private $tags;

You can read up about the fetch="EXTRA_LAZY" option here.

You can read up about indexBy="name" option here.

接下来,我修改了您的 addTag() 方法的版本,如下所示:

public function addTag(Tag $tags)
{
    // Check for an existing entity in the DB based on the given
    // entity's PRIMARY KEY property value
    if ($this->tags->contains($tags)) {
        return $this; // or just return;
    }
    
    // This prevents adding duplicates of new tags that aren't in the
    // DB already.
    $tagKey = $tag->getName() ?? $tag->getHash();
    $this->tags[$tagKey] = $tags;
}

注意:?? 空合并运算符需要 PHP7+。

通过将标签的获取策略设置为EXTRA_LAZY,以下语句使 Doctrine 执行 SQL 查询以检查数据库中是否存在具有相同名称的标签(有关更多信息,请参阅上面的相关 EXTRA_LAZY 链接):

$this->tags->contains($tags)

注意: 如果传递给它的实体的 PRIMARY KEY 字段被设置,这只能返回 true。当使用 ArrayCollection::contains() 等方法时,Doctrine 只能根据该实体的 PRIMARY KEY 查询数据库/实体映射中的现有实体。如果 Tag 实体的 name 属性只是一个 UNIQUE KEY,这可能就是它总是返回 false 的原因。您需要一个PRIMARY KEY 才能有效地使用contains() 等方法。

if block 之后 addTag() 方法中的其余代码通过 PRIMARY KEY 属性中的值( preferred if not null) 或 Tag 实体的散列(在 Google 中搜索“PHP + spl_object_hash”,Doctrine 使用它来索引实体)。因此,您正在创建一个索引关联,这样如果您在刷新前两次添加相同的实体,它只会在同一个键处重新添加,但不会重复。

关于php - 如何避免与 Doctrine 的多对多关系中的重复条目?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21712710/

相关文章:

php - mysql LIKE 只选择第一个字母开头的

php - 从重定向 URL 进行 Canvas 应用导航

postgresql - Doctrine2 使我对 OneToMany 和 ManyToOne 关系感到疯狂

php - ExpressionEngine no_results 不工作

symfony - 如何在 Symfony 2 中正确使用 PHPExcel

javascript - 更改G+ url参数而不刷新页面

php - Symfony Controller 不工作

php - 继承和关联场景中的 Dotrine 查询问题

doctrine-orm - Symfony3 - 添加记录 Doctrine 查询的记录器

PHP PDO 查询,参数位于数组中