Doctrine - 一个带有多个实体标签的表

时间:2018-02-10 18:38:58

标签: symfony doctrine-orm

我想创建具有分类词汇表的Tag实体类,将由多个实体使用。 是否有可能与三列建立多种关系? 我需要存储标签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列作为关键: &#34; tag_id&#34;,&#34; entity_name&#34;,&#34; record_id&#34;。 现在任何想法如何创建与任何实体的关系,标记表? 可能根本不需要标记实体吗?

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

1 个答案:

答案 0 :(得分:4)

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

正如您所说,您还可以为每个可以标记的实体提供单独的连接表。这是我目前在我的一个项目中使用的解决方案,它也正常工作。最后,它取决于您希望能够轻松执行的操作。

单个Tagging表/实体

您选择的这种方法也是FPNTagBundle中展示的方法,它集成了自己的DoctrineExtensions-Taggable库。它依赖于Tagging实体,该实体是与ManyToOneTag关联的拥有方,并且包含两个字段(resourceIdresourceType)请参阅标记实体。

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

<强>优点:

  • Tag可以与Tagging
  • 的关系相反
  • 轻松检索给定标记的标记实体的ID,与其类型无关
  • 即使类型不同,也很容易获得给定标记的标记实体的数量(您甚至可以通过简单的类别按类型计算)

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

<强>缺点:

  • 您的实体与TagTagging
  • 之间没有“真实”关联
  • Tagging与您的实体之间的关系没有外键,因此您的应用程序必须处理删除孤立Tagging条目(无法依赖ON DELETE CASCADE
  • 检索实体的代码需要extra query或自定义联接,其结果必须手动处理,以便在您的实体中有一个tags字段时正确保管。

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

使用这种方法,您最终会为每个实体提供一个连接表,声明与ManyToMany实体建立Tag关联。要快速向实体添加标签,我个人使用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位于反面,则需要为每个Taggable实体添加ManyToMany关联的反转
  • 您不能简单地检索给定标记的所有标记实体。对于给定类型
  • ,您只能以简单的方式执行此操作
  • 通常,执行作用于所有可标记实体的操作将是麻烦的(例如,我在项目中实现了标记的合并,因此我需要更新所有连接表。为此,我必须直接工作使用classmetadata并执行本机查询以在每个连接表上执行我的更新

无论如何,正如我在开始时所说的,这一切都取决于你想用标签执行什么样的操作。与resourceId实体内的两个字段resourceTypeTagging建立通用关系可能会让您在应用程序中获得更多灵活性,但在使用时也会有点棘手它涉及到为某些事情与学说合作。