如何避免在与教义的多对多关系中出现重复条目?



我正在使用嵌入的Symfony表单直接从文章编辑器中添加和删除Tag实体。Article是协会的拥有方:

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设置为唯一,因为每次在表单中输入相同的名称时,我都想使用相同的标签。但它不是这样工作的,我得到例外:

完整性约束冲突:1062 重复条目

我需要更改什么才能使用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">,在您的情况下是文章实体(您可能需要水平滚动代码块才能看到添加):

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

您可以在此处阅读有关fetch="EXTRA_LAZY">选项的信息。

您可以在此处阅读有关indexBy="name">选项的信息。

接下来,我修改了我的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)

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

addTag() 方法中 if 块之后的其余代码通过PRIMARY KEY属性中的值(如果不是 null 则首选)或Tag实体的哈希(在 Google 中搜索"PHP + spl_object_hash",Doctrine 用于索引实体)为 ArrayCollection of Tags 创建一个键。因此,您正在创建一个索引关联,因此,如果在刷新之前两次添加同一实体,则只会在同一键处重新添加该实体,而不会重复。

两个主要解决方案

第一

使用数据转换器

class TagsTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* used to give a "form value"
*/
public function transform($tag)
{
if (null === $tag) {
//do proper actions
}
return $issue->getName();
}
/**
* used to give "a db value"
*/
public function reverseTransform($name)
{
if (!$name) {
//do proper actions
}
$issue = $this->om
->getRepository('YourBundleName:Tag')
->findOneBy(array('name' => $name))
;
if (null === $name) {
//create a new tag
}
return $tag;
}
}

第二

使用生命周期回调。特别是,您可以使用prePersist触发器到您的article实体?通过这种方式,您可以检查预先存在的tags并让您的entity manager为您管理它们(这样他就不需要尝试持续导致错误)。

您可以在此处了解有关 prePersist 的更多信息

第二个解决方案的提示

创建自定义存储库方法以搜索和获取旧标签(如果有)

相关内容

  • 没有找到相关文章