-
Notifications
You must be signed in to change notification settings - Fork 11.5k
Description
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 currentmorphTo
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.