Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
driesvints opened this issue Aug 5, 2013 · 52 comments
Closed

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

driesvints opened this issue Aug 5, 2013 · 52 comments

Comments

@driesvints
Copy link
Member

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: driesvints@2f3af99

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

@DanSmith70
Copy link
Contributor

Big thumbs up for this, super useful. I haven't gone in depth on the code behind it but the API you describe looks great to me.

@samgreenwood
Copy link
Contributor

Looks great to me +1

@rhulshof
Copy link

rhulshof commented Aug 7, 2013

Awesome addition! I really hope this will be added to Laravel 4 Master a.s.a.p.!

@ashep
Copy link

ashep commented Aug 7, 2013

Super useful!

@BastianHofmann
Copy link
Contributor

Sounds solid. If I remember correctly Taylor wanted to implement this functionality anyways.

@mikedfunk
Copy link

+999

Mike Funk
mikefunk.com | mike@mikefunk.com

On Wed, Aug 7, 2013 at 8:37 AM, BastianHofmann notifications@github.comwrote:

Sounds solid. If I remember correctly Taylor wanted to implement this
functionality anyways.


Reply to this email directly or view it on GitHubhttps://github.com//issues/2032#issuecomment-22247810
.

@rhulshof
Copy link

rhulshof commented Aug 8, 2013

Great!!!

What is the expected timeline for this addition? Is it feasible to have this pushed into Master in say... a week? I really need this functionality. Adding it manually to /vendor/laravel won't work, since I heavily use "composer update".

@driesvints
Copy link
Member Author

Can't say when it's going to be added (or if). Taylor will need to take a look at it first when he finds some time. I don't think this will be added within a week.

@barryvdh
Copy link
Contributor

barryvdh commented Aug 8, 2013

You can probably just extend the Model and add the classes to an autoloaded directory. Or if Taylor doesn't like it, this can be created as a package, right?

@rhulshof
Copy link

rhulshof commented Aug 8, 2013

Would be great if this becomes available as a package. In that way it's quite easy to convert as soon as Laravel natively supports this functionality.

@DanSmith70
Copy link
Contributor

@rhulshof What ever happens this would never go in to the 4.0.x branch, it would go in to 4.1 which is slated for November currently.

@Unprofound
Copy link

+1 Package or Native. I would be working on something similar to later this year.

@ammont
Copy link

ammont commented Aug 14, 2013

I guess it's better to be package.

@taylorotwell
Copy link
Member

I'm looking at this. Just FYI.

@ccovey
Copy link
Contributor

ccovey commented Aug 17, 2013

For the people needing this soon you could create your own implementation adn just have your model extend the custom code instead of Eloquent/Model . Then change over if this becomes available. No need to edit vendor folders....

@taylorotwell
Copy link
Member

What makes you think I won't have it ready soon? :)

Taylor Otwell
Sent with Airmail

On August 16, 2013 at 10:09:13 PM, Cody Covey (notifications@github.com) wrote:

For the people needing this soon you could create your own implementation adn just have your model extend the custom code instead of Eloquent/Model . Then change over if this becomes available. No need to edit vendor folders....


Reply to this email directly or view it on GitHub.

@rhulshof
Copy link

Niicceeeee!

Does this also mean Eager Loading will work on Polymorphic relations, like described here: https://github.com/laravel/laravel/issues/1681#issuecomment-17104738 ?

@HellPat
Copy link
Contributor

HellPat commented Aug 26, 2013

Nice!

@ghost
Copy link

ghost commented Aug 26, 2013

+1

@andrewmclagan
Copy link

This would make my day...

@andrewmclagan
Copy link

+1000

@lozymon
Copy link

lozymon commented Sep 1, 2013

+1

@driesvints
Copy link
Member Author

He's working on it folks. No need to keep +1'ing it.

@taylorotwell
Copy link
Member

Especially no need since its already done and in master branch lol.

On Sep 1, 2013, at 4:16 PM, Dries Vints notifications@github.com wrote:

He's working on it folks. No need to keep +1'ing it.


Reply to this email directly or view it on GitHub.

@taylorotwell
Copy link
Member

But yeah +1 just spams my inbox. You don't have to do it.

On Sep 1, 2013, at 4:16 PM, Dries Vints notifications@github.com wrote:

He's working on it folks. No need to keep +1'ing it.


Reply to this email directly or view it on GitHub.

@anlutro
Copy link
Contributor

anlutro commented Sep 15, 2013

Is this finished at the moment? I can't get inverse relationships to work. I have a Permission model which is related to many different models, but the morphedByMany method takes a 'related' argument which it tries to instantiate instead of looking in the database for the correct model type.

public function resources()
{
    return $this->morphedByMany('???', 'resource', 'permission_resource');
}

My Resource->Permission relationship works fine, though.

public function permissions()
{
    return $this->morphToMany('Permission', 'resource', 'permission_resource');
}

@taylorotwell
Copy link
Member

The inverse isn't implemented yet. I'm going to try to take a stab at it this week. It's complicated... to say the least.

@MacTEC
Copy link
Contributor

MacTEC commented Oct 7, 2013

@taylorotwell Hi, is there any update on this issue? We can't wait to use this new feature.

@pilot911
Copy link
Contributor

nice feature!

@bgallagh3r
Copy link

I hope this is available for 4.1. I'm actually working on a project that could really use this.

@hasokeric
Copy link

There is a blog post that states this will be in 4.1, true?

@rhulshof
Copy link

True. You can already check it out by installing 4.1 using Composer:

require: "laravel/framework": "4.1.*"

Upgrade guide

@BenjaminRH
Copy link

This isn't listed in the 4.1 docs or the release notes, was this included?

@pilot911
Copy link
Contributor

yes, it was included

@thomasjonas
Copy link

Can somebody whip up some kind of example? (And submit it to the docs!) I'm not sure how to properly implement this.

@pilot911
Copy link
Contributor

@thomasjonas
Copy link

Awesome! Thanks!

@sebastiaanluca
Copy link
Contributor

So there's no way to get all posts or pages related to a tag using something like morphToPivot? I thought this was the reason for this polymorphic many-to-many feature?

@franksamsung
Copy link

Hey, i'm in the middle of implementing this technique and just trying to work out if the end result is efficient. I can understand the database will be simpler, but with the alternative use of pivot tables you are able to set foreign keys.

@atrauzzi
Copy link
Contributor

Does this support fetching the mixed results?

@pilot911
Copy link
Contributor

Does this support fetching the mixed results?

@rhulshof
Copy link

Yes it does.

It however does not support nested relationships through the Polymorphic ones.

@cmarfil
Copy link

cmarfil commented Oct 17, 2014

What is the way to make a query using a morph relationship?

Example (on user model): $this->morphedByMany('User', 'locationable');

$users = User::whereHas('locations', function($q){
   $q->where('state_id', '=', 8);
});

the back does not work :S

@gizburdt
Copy link
Contributor

@driesvints Sorry for the very late comment, but is there something like this in core, or as a module?

UPDATE: Sorry, found it already, awesome feature! :)

@driesvints
Copy link
Member Author

@gizburdt glad you found it :)

@federico77
Copy link

Hello, after some digging I still could not find any information about retrieving the inverse of a many-to-many polymorphic relation that allows mixed results to be retrieved.

Please consider the following:

I have several models that can be "tagged". While it is trivial to retrieve $item->tags, $article->tags and the inverse with $tag->articles and $tag->itemsI have no easy way to do something like $tag->taggables to return both articles and items in the same collection. Things get even bumpier as I need to use pagination/simple pagination to the query.

@driesvints any help/code snippet that you can share about this? I am currently using 5.1 and I am a bit stuck.

@chrisschaetzlein
Copy link

+1 me on what @federico77 said. I'm also stuck there. I'd like to get $tag->taggables() return all the different models related to the Tag. Eloquent doesn't seem to be able to handle this kind of relation yet.

@zolotov88
Copy link

+1

@bahriddin
Copy link

Here I found a solution.

@bhulsman
Copy link

bhulsman commented Jun 16, 2017

It would indeed be very nice to be able to fetch all taggables (in the tags example). Using something like return collect($this->getRelations())->collapse(); works, but seems a bit dirty. Especially if you need some form of sorting, in my case on the pivot table.

@tobz-nz
Copy link
Contributor

tobz-nz commented Jul 18, 2018

Looks like the inverse relation is still not implemented :(
@taylorotwell @themsaid is this in the too hard basket or overlooked or...?

@LasseRafn
Copy link
Contributor

LasseRafn commented Apr 26, 2019

👋 - would gladly help with this, but fearing my thoughts are more harm than good. I have no clue how this would work.. Looked at core, and the whole concept seems super complicated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests