【问题标题】:Doctrine - one table with tags for multiple entitiesDoctrine - 一张带有多个实体标签的表格
【发布时间】:2018-02-10 18:38:57
【问题描述】:

我想创建标签实体类,带有分类词汇,将被多个实体使用。 有没有可能用三列创建多对多关系? 我需要存储标签 ID、实体 ID 和实体类名。

所以我现在有:

<?php
/**
 * Taxonomy
 *
 * @ORM\Table(
 *    name="tag_taxonomy",
 *    indexes={
 *        @ORM\Index(name="tag_taxonomy_namex", columns={"name"})
 *    }
 * )
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity
 */
class Taxonomy
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=50)
     */
    protected $name;
    /**
     * @ORM\OneToMany(targetEntity="Tag", mappedBy="taxonomy", fetch="LAZY")
     */
    protected $tags;
    // .....
}

标签表:

<?php
/**
 * Tag
 *
 * @ORM\Table(
 *     name="tag",
 *    indexes={
 *        @ORM\Index(name="namex", columns={"name"})
 *    }
 * )
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity
 */
class Tag
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue
     */
    protected $id;
    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=50)
     */
    protected $name;
    /**
     * @ORM\ManyToOne(targetEntity="Taxonomy", inversedBy="tags", fetch="LAZY")
     * @ORM\JoinColumn(name="taxonomy_id", referencedColumnName="id", onDelete="SET NULL")
     */
    protected $taxonomy;
    // ....
}

关系表:

<?php
/**
 * @ORM\Table(
 *     name="tag_tagging",
 *     uniqueConstraints={
 *        @ORM\UniqueConstraint(name="tagging_idx", columns={"tag_id", "entity_name", "record_id"})
 *    },
 *    indexes={
 *        @ORM\Index(name="entity_name_idx", columns={"entity_name", "record_id"})
 *    }
 * )
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity
 */
class Tagging
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
    /**
     * @ORM\ManyToOne(targetEntity="Tag", inversedBy="tagging", cascade="ALL")
     * @ORM\JoinColumn(name="tag_id", referencedColumnName="id", onDelete="CASCADE")
     **/
    protected $tag;
    /**
     * @var string
     * @ORM\Column(name="entity_name", type="string", length=100)
     */
    protected $entityName;
    /**
     * @var int
     * @ORM\Column(name="record_id", type="integer")
     */
    protected $recordId;
    // .....
}

所以我已经有了以 3 列为键的关系表: “tag_id”、“entity_name”、“record_id”。 现在知道如何与任何实体创建关系来标记表吗? 也许根本不需要标记实体?

我想知道为任何实体类型创建标签实体的最佳方法是什么。 所以我可以为用户、文章和任何东西使用标签。 当然,可以有许多关系将为每个实体创建单独的关系表,对吧?但这是正确的方法吗?

【问题讨论】:

  • 您是否需要Tag 以某种方式同时成为所有这些关系的反面?只是问,因为您将 inversedBy="tagging" 放入您的 Tagging 实体中。
  • 建议你看看this。这很好地说明了为什么 Doctrine 中没有与额外字段的多对多关系。遵循他们的方法将使您的编码生活更加轻松。
  • @AlanT。我没有。
  • @geoB 也许这是一个很好的例子。两个多对一而不是一个多对多可能是正确的方法;)我会检查它
  • 也许不是正确的主题,但这些天我使用简单的文本数组或 jsonb 字段来处理这些事情。我说的是 PostgreSQL。

标签: symfony doctrine-orm


【解决方案1】:

您的方法完全没问题,即使它不允许在任何实体和Tagging 类之间创建真正的关联(使用底层外键)。不过,这是完全可以接受的。您需要一种方法来定义将进入您的 entityName 字段的内容,以便它可以有效地充当鉴别器字段。

正如您所说,您还可以为每个可以标记的实体创建一个单独的连接表。这就是我目前在我的一个项目中使用的解决方案,它也运行良好。最终,这取决于您希望能够轻松执行什么操作。

单个Tagging表/实体

您选择的这种方法也是FPNTagBundle 中介绍的方法,它集成了自己的DoctrineExtensions-Taggable 库。它依赖于 Tagging 实体,该实体是与您的 Tag 表关联的 ManyToOne 的拥有方,并包含两个字段(resourceIdresourceType)来引用标记的实体。

如果你想走这条路,我建议你看看 FPNTagBundle 的 Taggable 原则扩展实现。或者,您可以查看this other taggable extension。它非常相似,但它以不同的方式使用原则事件来处理保存/加载。

优点:

  • Tag 可以与 Tagging 的关系相反
  • 轻松检索给定标记的标记实体的 ID,与它们的类型无关
  • 即使类型不同,也可以轻松获取给定标记的标记实体计数(您甚至可以通过简单的分组方式按类型获取计数)

查看两个扩展提出的存储库:herehere

缺点:

  • 您的实体与TagTagging 之间没有“真实”关联
  • Tagging 和您的实体之间的关系没有外键,因此您的应用程序必须处理孤立的 Tagging 条目的删除(无法依赖 ON DELETE CASCADE
  • 检索实体的标签需要extra query 或自定义连接,如果您想在实体中添加tags 字段,则必须手动处理其结果。

每个可标记实体拥有的ManyToMany 关联

使用这种方法,您最终会为每个声明与您的 Tag 实体的 ManyToMany 关联的实体创建一个连接表。为了快速为实体添加标签,我个人使用TaggableTrait,如下所示:

trait TaggableTrait
{    
    /** @ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag\Tag") */
    private $tags;

    /* ... getter and add/remove methods ... */
}

这样,在我的实体中使用简单的use TaggableTrait 语句,我可以启用标记。

优点:

  • 易于设置
  • 您可以通过简单的连接直接检索实体的标签
  • 您可以使用additional conditions specified by WITH直接按标签过滤实体
  • 借助连接表的外键,您可以依靠数据库来删除孤立标记关联

缺点:

  • 如果您希望 Tag 位于反面,则需要为每个可标记实体添加 ManyToMany 关联的反面
  • 您不能简单地检索给定标记的所有标记实体。对于给定的类型,您只能以简单的方式完成
  • 一般来说,对所有可标记实体执行操作会很麻烦(例如,我在项目中实现了标签的合并,因此我需要更新所有连接表。为此,我必须直接工作使用 classmetadata 并执行本机查询以在每个连接表上执行我的更新)

无论如何,正如我一开始所说,这一切都归结为您想对标签执行什么样的操作。与 Tagging 实体内的两个字段 resourceIdresourceType 建立通用关系可能会给您的应用程序带来更多的灵活性,但在使用原则时也会有点棘手对于一些事情。

【讨论】:

  • 感谢您非常好的解释。我可以补充两点。 1. 单标签表/实体 - 如果我们想通过管理面板提供动态标签添加,这是一个好方法。因此用户可以选择可标记的实体。使用 ManyToMany 关联是不可能的。 2. ManyToMany 我们当然可以使用trait,但也可以通过使用@MappedSuperclass 注解扩展类来完成:docs.doctrine-project.org/projects/doctrine-orm/en/latest/…
猜你喜欢
  • 2012-03-06
  • 1970-01-01
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-04
  • 2016-07-02
相关资源
最近更新 更多