framework: [5.4] Custom Intermediate (Pivot) Models not instantiable

  • Laravel Version: 5.4.9
  • PHP Version: 7.1
  • Database Driver & Version: MySQL 5.7.16

Description:

When trying to use a custom intermediate model (a model that extends from Illuminate\Database\Eloquent\Relations\Pivot) as a regular model (Pivot extends from Model), it fails, because is not able to create an instance.

Too few arguments to function Illuminate\Database\Eloquent\Relations\Pivot::__construct(), 0 passed

I checked the commit but did not found anything would help on this: 4439576c9a9da5d6dcf8d4fd3ab4210576450eb6

Steps To Reproduce:

Create a model that extends from Pivot instead of Model directly:

...
use Illuminate\Database\Eloquent\Relations\Pivot;
...
class CustomIntermediate extends Pivot
{
    ......
}

Then try to use the model for some specific functions like all(), with(), query(), etc.

Route::get('test', function() {
    return CustomIntermediate::all();
});

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 11
  • Comments: 36 (19 by maintainers)

Most upvoted comments

I think the document should show a practical example using intermediate table.

Guys, agreed, some documentation would be nice, but after toying with it, here’s how it works.

Let’s say M-M for User and Books. And you’re also adding a store_id attribute to the pivot table. How can you get that Store model based on the store_id?

// User.php

public function books()
{
    return $this->belongsToMany(Book::class)
                ->withPivot('store_id')
                ->using(BookUser::class);
}

So now when you hit $user->books->first()->pivot you’re getting the BookUser model. Remember, $user->books returns a collection. You want a Book from the collection to make use of BookUser. (…so above i just used ->first())

// BookUser.php
class BookUser extends Pivot {
    public function store()
    {
        return $this->belongsTo(Store::class);
    }
}

Bingo. Now… $user->books->first()->pivot->store yields the Store of store_id.

So now you might use it for something like this…

@foreach ($user->books as $book)
  Store name: {{ $book->pivot->store->name }}
@endforeach

So that’s it. Each row in the pivot table is now coming back to you as a BookUser model. And you can do relations, saves, updates, whatever.

I hope that helps.

Awesome! Thank you @BertvanHoekelen

Is there any progress? Getting this issue as well.

This proposal solved it for me @decadence @a-komarev : https://github.com/laravel/internals/issues/175

Read the comments all the way through. It explains the changes required to eager load a pivot model relationships on 5.4.

@JuanDMeGon +1 for posting. I’ve gone hunting for this a couple of times; glad to see it make its way into the 5.5 release. Can anyone confirm if this addition to 5.5 will also allow for eager loading?

eg, @rdpascua’s example: $foo->with('bar.pivot.baz');

i’m having the same issue, i can’t instantiate a pivot model, here’s my structure and the use case

posts (belongsToMany tags)

  • id
  • name

tags (belongsToMany posts)

  • id
  • name

post_tag (pivot)

  • id
  • post_id
  • tag_id
  • votes_count

votable (polymorphic)

  • id
  • votable_id
  • votable_type

and as the tables explain by themselves, you can vote for a post_tag just like the likedin tag recommandation feature

the api would be something like : POST /post-tag/{post_tag}/likes And inside this something like

$post_tag = PostTag::findOrFail($id);
$post_tag->votes()->save(new Vote)

But this doesn’t work because you can’t instantiate, the only thing i can do now is

POST /post/{post}/tag/{tag}/likes

$post = Post::findOrFail($id);
$post_tag = $post->tags()->findOrFail($tag)->pivot;
$post_tag->votes()->save(new Vote);

that’s so much cumbersome and so less performant as you have so many queries here while the only info needed is the pivot table record to save the associated vote, and not the tag record nor the post record

@dherran thanks. But would be cool to have this out of box.

I face similar issue but it’s expected behavior because Pivot constructor needs some values indeed. Would be nice if we can use it as normal Model.

Now how can we eagerload the intermediate table?

$foo->with('bar.pivot.baz'); doesn’t work since pivot relation doesn’t exists.

If you check the definition of Illuminate\Database\Eloquent\Relations\Pivot it extends from eloquent Model, so in my understanding it still being a regular model. The custom pivot model acts, as as you said, like a pivot AND is a model too I should be able to use it directly or to resolve the many to many relationship through it. Maybe I am misunderstanding something here but it does not seem logic for me to use a custom pivot model and not be able to use it as a regular model as well. Possibly @pstephan1187 can give us some orientation for this?