Skip to content

[Request] Add Polymorphic Many-to-Many relationships #2032

@driesvints

Description

@driesvints

I've been working on an implementation for this a few months back but haven't really had the time yet to finish it up. Since this is a pretty high-requested feature (#1698, #1922) I thought I'd open a new issue to show what I've got so far.

The Problem

The idea behind a polymorphic many-to-many relationship is simple. Let's take a tags table as an example. You'll probably want tags to be attached to your posts table as well as your pages table, etc... Currently you'd have to implement 2 different belongsToMany relationships on your Tag model. While this is fine as long as you only have 2 or 3 relationships, things can quickly become cumbersome if you have to attach yours tags table to a lot of other models. You also don't have a single way to check for relationships, you'd always have to check each table separately.

The Solution

The solution is fairly simple. Add a new polymorphic many-to-many relationship where the polymorphic relationships are set on the pivot table, rather than the polymorphic model.

The database scheme would look as follows:

tags
    - id
    - title

tag_tagable (pivot)
    - id
    - tag_id
    - tagable_id
    - tagable_type

posts
    - id
    - title

pages
    - id
    - title

Possible records for the pivot table:

id | tag_id | tagable_id | tagable_type
-- | ------ | ---------- | ------------
1  | 1      | 1          | Post
2  | 1      | 1          | Page
3  | 2      | 1          | Post
4  | 3      | 2          | Page
5  | 2      | 2          | Post

Because we put the polymorphic relationship columns on the pivot table we get a very flexibly way to set relationships towards other models.

Implementing the relationships on models could look as follows:

class Tag extends Eloquent {

    public function tagable()
    {
        return $this->morphToPivot();
    }

}

class Post extends Eloquent {

    public function tags()
    {
        return $this->morphBelongsToMany('Tag', 'tagable');
    }

}

class Page extends Eloquent {

    public function tags()
    {
        return $this->morphBelongsToMany('Tag', 'tagable');
    }

}

The reason why I've created a new function here (morphToPivot) is because the current morphTo function assumes the polymorphic relationships are on the called model and not on the pivot table.

Now that we've set the relationships, we can begin using them. For instance if we'd want to grab all of the records attached to a single tag:

foreach ($tag->tagable as $tagable)
{
    if ($tagable instanceof Post)
    {
        echo 'This is a Post model';
    }

    elseif ($tagable instanceof Page)
    {
        echo 'This is a Page model';
    }
}

If we want to grab all of the tags for a single page or post:

foreach ($post->tags as $tag) {}
foreach ($page->tags as $tag) {}

You see? You get all the benefit of many-to-many relationships and polymorphic relationships.

This also solves the problem for checking relationships on each table separately. For example, if you'd want to check how many items are attached to a single tag you could do this:

$count = $tag->tagable()->count();

Current Progress

Now, I've already been working on the implementation for this and I've got it finished for about 50% so far. Everything for the morphBelongsToMany relationships works so far.

The only thing that still needs to be done is the morphToPivot function so it works the other way around as well.

You can find all of my code in this commit: https://github.com/driesvints/framework/commit/2f3af990860a08ac37ab5dab71207ff39944e8d6

I'd really like opinions on this and what you think would improve this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions