Search code examples
phplaravellaravel-5.3

Laravel 5.3 Polymorphic many to many - how to get all the related rows regardless of their table sorted by pivot column?


I have a model Page and many models called SomethingSection - they're connected through a polymorphic m-m realtionship and the pivot has an additional column 'position'.

I need to write a relationship (or accessor maybe?) on the Page model that will return a collection of all connected Sections, regardless of their model (read: table).

My models:

class Page extends Model {

    public function introSections()
    {
        return $this->morphedByMany(IntroSection::class, 'pagable');
    }

    public function anotherSections()
    {
        return $this->morphedByMany(AnotherSection::class, 'pagable');
    }
}

class IntroSection extends Model {

    public function pages()
    {
        return $this->morphToMany(Page::class, 'pagable');
    }
}

class AnotherSection extends Model {

    public function pages()
    {
        return $this->morphToMany(Page::class, 'pagable');
    }
}

The pivot column looks like this:

pagables
    -page_id
    -pagable_id
    -pagable_type
    -position

I'm looking for a way to call a method/attribute on the Page model and get all the connected sections in a single collection, sorted too. What would be a good way to go about this?

I understand that the connected sections do not have the same interface, but in my case that's not a problem at all (in terms of what I will do with the data).

I also understand that relationships perform a separate query (for each relationship), so getting all of them with 1 query is impossible (also different interfaces would be a problem here). And for the same reason the sorting will need to be done on the collection level, not in query.

How could I make this as maintainable as possible and preferably with as small a performance hit as possible.

Thanks in advance.


Solution

  • You can use withPivot() method after your relationship to get the pivot columns with relation like this:

    class Page extends Model {
    
        public function introSections()
        {
            return $this->morphedByMany(\HIT\Models\Sections\IntroSection::class, 'pagable')
                                    ->withPivot(['position']);
        }
    
        public function anotherSections()
        {
            return $this->morphedByMany(AnotherSection::class, 'pagable');
        }
    }
    
    class IntroSection extends Model {
    
        public function pages()
        {
            return $this->morphToMany(Page::class, 'pagable')
                                    ->withPivot(['position']);
        }
    }
    

    and you can use collection's sortBy to sort the collection by using sortBy() method like this:

    $sorted_collection = IntroSection::pages->sortBy('pagables.position');
    

    UPDATE:

    You can use collection's combine() method to get all the relationships like this, add this method inside your Page Class:

    public function getAllSections()
    {
        return $this->introSections->combine($this->anotherSections-toArray())
                    ->sortBy('pagables.position'):
    }
    

    Hope this helps!