staudenmeir / eloquent-has-many-deep

Laravel Eloquent HasManyThrough relationships with unlimited levels
MIT License
2.67k stars 157 forks source link

feature request (/ advice?): multiple polymorphic relationships #184

Closed sidewaysglance closed 1 year ago

sidewaysglance commented 1 year ago

Hi

I have a situation where I have been asked to provide functionality that could create links from "anything to anything".

For example with a normal polymorphic relationship, I might be needing to link a comment to either a song or a book.

But what I need to do is, with one table, link a song to a book, link a book to a comment, link anything to anything. Something like:

id link_from_type link_to_type link_from_id link_to_id
1 App/Models/Post App/Models/Book 23 54
2 App/Models/Song App/Models/Book 86 54

I have been using this: https://github.com/omaximus/laravel-pivot-polymorph

But the documentation is v poor, I don't fully understand it, & I don't need much of what it has to offer.

Can anyone suggest another solution?

lrljoe commented 1 year ago

Depends how many models you have. You could just have a links table for each model and have your morphs to other models in there? That'd seem to be the simplest way in my mind at least.

E.g songs (id, other fields) song_links (contains song_id, linkable_id, linkable_type) books (id, other fields) book_links (contains book_id, linkable_id, linkable_type)

You could of course then use something like https://github.com/chelout/laravel-relationship-events to add the inverse of the relationship whenever an event happens, so that it exists on both sides, for ease of retrieval etc.

sidewaysglance commented 1 year ago

Mm the thing is there are currently 10 models, so currently 10*10 polymorphic m2m relationships. In the future I can see there might be many more and it will be hugely unwieldy.

Ideally I would be able to use a trait on models which would provide the functionality I am after.

I am working with https://github.com/omaximus/laravel-pivot-polymorph, it does work as described, but it's immature, I have to keep a maintain a fork which I'm updating with framework versions etc.

staudenmeir commented 1 year ago

Hi @sidewaysglance, This is not possible with the package at the moment.

I am working with https://github.com/omaximus/laravel-pivot-polymorph, it does work as described

Can you share the relationship you are using here?

sidewaysglance commented 1 year ago

Hi, I am using morphsTo() in a trait:

    public function connections()
    {
        return $this->morphsTo('link_from', 'link_to', 'connections');
    }

There is a lot more going off in there which I don't understand but might be really useful. In an ideal world it would be possible to store a string with the connection, again I don't think it's possible.

They are based in Ukraine and don't feel happy chucking GitHub issues at them when they probably have a lot on their minds.

If someone would be willing to take it on I would be willing to pay low hundreds of gbp to support development / documentation

lrljoe commented 1 year ago

I'd still suggest a morph for each model with its own table in the interim.

It's easily stubbable, so you could generate the tables and relations using commands to save yourself a load of pain.

staudenmeir commented 1 year ago

@sidewaysglance Thanks for your generous donation! I'll look into this topic.

staudenmeir commented 1 year ago

I found an approach using one of my other packages that merges multiple relationships with SQL views: https://github.com/staudenmeir/laravel-merged-relations

For everyone of your connected models, you need to create a view that merges native MorphToMany relationships to all the other models (Post -> Book, Post -> Song etc.).

  1. Create a migration with this and add your models to the array:
use Staudenmeir\LaravelMergedRelations\Facades\Schema;

$models = [Book::class, Post::class, Song::class]; // TODO

foreach ($models as $model) {
    $relations = [];

    foreach ($models as $relatedModel) {
        if ($model !== $relatedModel) {
            $relations[] = (new $model)
                ->morphToMany($relatedModel, 'link_from', 'connections', 'link_from_id', 'link_to_id')
                ->where('link_to_type', $relatedModel);
        }
    }

    Schema::createOrReplaceMergeView(
        (new $model)->getTable() . '_connections',
        $relations
    );
}
  1. Create a trait and use it in all your connected models:
use Staudenmeir\LaravelMergedRelations\Eloquent\HasMergedRelationships;

trait HasConnections
{
    use HasMergedRelationships;

    public function connections()
    {
        return $this->mergedRelation($this->getTable() . '_connections');
    }
}
class Post extends Model
{
    use HasConnections;
}
Post::with('connections')->get();

It's not as elegant as with the other package, but you don't have to worry about the ongoing Laravel support.

lrljoe commented 1 year ago

Might make a quick command for generating this to save manual intervention for the future.

sidewaysglance commented 1 year ago

Thanks for the advice all!

All the best