Search code examples
phpsymfonydoctrine-ormpolymorphic-associations

What is the role played by the `type` column in Doctrine class table inheritance?


Using Symfony 4 / Doctrine 2.6. I have two entities Post and Comment. I want both to be taggable. So I create an entity Tag. I use Doctrine's class table inheritance to create the relationship:

/**
 * @ORM\Entity(repositoryClass="App\Repository\TagRepository")
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({"post" = "PostTag", "comment" = "CommentTag"})
 */
abstract class Tag
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $title;

    // Getters and setters...
}

/** @ORM\Entity */
class PostTag extends Tag
{
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="tags")
     */
    private $post;

    public function getPost(): ?Post
    {
        return $this->post;
    }
}

/** @ORM\Entity */
class CommentTag extends Tag
{
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Comment", inversedBy="comments")
     */
    private $comment;

    public function getComment(): ?Comment
    {
        return $this->comment;
    }
}

This creates 3 tables: tag, post_tag, and comment_tag. The post_tag table structure looks like this:

id | post_id

The tag table structure looks like this:

id | title | type

How are e.g. posts and tags associated? If I want to associate post 13 with tag test, would the result be this:

post_tag table:

id | post_id
------------
1  | 13

tag table:

id | title | type
-----------------
1  | test  | post

?

If so, then what if I want to associate the same tag (test) with a comment. Would the tag table then look like this?

id | title | type
--------------------
1  | test  | post
2  | test  | comment

That seems a bit redundant. The same entity (the test tag) is then represented by 2 rows in the tag table. Am I getting this wrong?


Solution

  • tl;dr: inheritance is the wrong tool. A tag is a tag is a tag. Inheritance is inherently provided by using multiple associations (many-to-many).

    Your inheritance essentially says: There are two distinct types of tags, that are essentially different. One tag, that can be applied to posts, and one tag, that can be applied to comments, and those aren't the same tags, but different tags.

    Since both kinds of tags are stored in the same table, there must be some mechanism on how to discriminate one from the other. That's what the type column is for. (So, this is essentially the answer to your main question, afaict)

    So essentially, if you want to tag comments and blog (posts) alike, these are the more common options:

    1. Tags: (tag_id, tag_name, whatever), Comment_tags: (tag_id, comment_id), Blog_tags: (tag_id, blog_id) ... (I assume, this is what you want/need)
    2. Tags: same, Tagassignments: (tag_id, object_type, object_id) ... (inconvenient in Doctrine, and overall unfavorable¹)

    You chose a different approach: Tags: (tag_id, tag_name, object_type), Tagassignments: (tag_id, object_id) (<-- object type is implicitly given by tagid, but since you're using relations, Tagassignments is split up into blog_tags and comment_tags)

    However, as Magnus Eriksson correctly commented, this might make sense. I have my doubts. I assume, that either option 1 or option 2 are much more common and convenient. And you should drop the inheritance on the Tag and instead add inheritance on the association (if needed, you'd need to make it an extra Entity to make it work), but rather advise on chosing option 1 because it's waaay easier to implement with doctrine and it's annotations. (although you'd need to add one get{Object}s() for each different object type that is supposed to be taggable.)

    ¹as Magnus correctly comments below (and co-commented by me): You lose most advantages databases provide, mainly performance, clarity and consistency. I would generally advise against this approach.