Sometimes you may wish to use custom pivot model when working with many-to-many relationships in Eloquent, in order to add some behaviour on top of the core features.
There are a few things to consider before you start.
First things first: belongsToMany
relationship can presented as double hasMany
+ belongsTo
relations.
Imagine we have categories of posts and users who can subscribe for multiple categories:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
User belongsToMany Category // the same as: User hasMany CategoryUser CategoryUser belongsTo User Category hasMany CategoryUser CategoryUser belongsTo Category // CategoryUser is additional model for the pivot table: use Illuminate\Database\Eloquent\Model; // * class CategoryUser extends Model { protected $table = 'category_user'; // ... relations } |
* I prefer to use Model
class name instead of its Eloquent
alias.
This additional model can be used if we need to hook on the pivot table events (what cannot be done on the relation itself – read here for a workaround https://stackoverflow.com/a/28934080)
Another use case would be pivot table that links more than just 2 other tables.
However, this model is just like any other and it is not used by Eloquent, when we call belongsToMany
relation on the user or category. So next, let’s define that custom pivot model (we can use both, or just one of these, depending on the needs):
1 2 3 4 5 6 7 8 |
use Illuminate\Database\Eloquent\Relations\Pivot; class CategoryUserPivot extends Pivot { // let's use date mutator for a field protected $dates = ['completed_at']; } |
Now, in order to let Eloquent grab this pivot model, we also need to override newPivot
method on both User
and Category
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// User model public function newPivot(Model $parent, array $attributes, $table, $exists) { if ($parent instanceof Category) { return new CategoryUserPivot($parent, $attributes, $table, $exists); } return parent::newPivot($parent, $attributes, $table, $exists); } // Category model public function newPivot(Model $parent, array $attributes, $table, $exists) { if ($parent instanceof User) { return new CategoryUserPivot($parent, $attributes, $table, $exists); } return parent::newPivot($parent, $attributes, $table, $exists); } |
The if statement is required, so only this particular relation grabs our custom pivot model. Obviously we don’t want it to work for other m-m relations if it is specific to user-category only.
Basically this is all we have to do, so let’s check it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
>>> $user = User::first() >>> $category = $user->categories->first() >>> $category->pivot // our custom pivot => <App\Models\CategoryUserPivot #0000000052d0621800000000619c04ac> { user_id: 1, category_id: 20,cutsom completed_at: "2015-05-02 13:41:31" } >>> $category->pivot->completed_at // mutators working as usually => <Carbon\Carbon #0000000052d0623800000000619c15cc> { date: "2015-05-02 13:41:31.000000", timezone_type: 3, timezone: "Europe/Warsaw" } // the same for other side of the relation >>> $cat = Category::first() >>> $cat->users->first()->pivot => <App\Models\CategoryUserPivot #0000000052d0621200000000619c04ac> { category_id: 1, user_id: 9, completed_at: "2015-03-18 15:24:09" } |
As you can see all is working as expected. Now, you’re free to add custom behaviour to the pivot model, so it will work best for you.
There is just one more thing to remember – this custom pivot model won’t be used during sync
, attach
, detach
calls – these are simple queries not touching eloquent.
Share your thoughts and questions in the comments!