This article was published over 2 years ago. Some information may be outdated.
By default, Laravel does not observe pivot tables. To understand why, look at the following tables:
posts
- id
- title
- content
- ...
tags
- id
- name
- ...
post_tag
- id
- post_id
- tag_id
The post_tag is a pivot table, and therefore it does not have a model by default.
Here is the relationship:
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}
To determine the relationship's intermediate table name, Eloquent joins the two related model names in alphabetical order. In this case, it is post_tag since P comes before T in the English alphabet.
Read more about Eloquent relationships
Now create an observer for the post_tag table so you get notified whenever a user attaches a tag to a post.
First, create a model for post_tag because pivot tables do not have one by default:
php artisan make:model PostTag
The model should look like this:
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class PostTag extends Pivot
{
protected $table = 'post_tag';
public $timestamps = null;
}
The PostTag model must extend Illuminate\Database\Eloquent\Relations\Pivot and not Illuminate\Database\Eloquent\Model since it is a pivot table. Otherwise, you will get an error like this:
BadMethodCallException
Call to undefined method App\Models\PostTag::fromRawAttributes()
The second step is to create an observer for the PostTag model:
php artisan make:observer PostTagObserver --model=PostTag
Add the observer to the boot method in AppServiceProvider:
PostTag::observe(PostTagObserver::class);
Open the PostTagObserver and add the following code to log the PostTag model in the created event:
namespace App\Observers;
use App\Models\PostTag;
class PostTagObserver
{
public function created(PostTag $postTag)
{
logger($postTag);
}
}
Test it:
# Filename: routes/web.php
Route::get('/observer', function() {
$post = \App\Models\Post::find(1);
$tag = \App\Models\Tag::inRandomOrder()->first();
$post->tags()->attach($tag->id);
});
Open the storage/logs/laravel.log file. Nothing is there.
The problem is that Laravel does not know about the PostTag model you created for the pivot table post_tag. Laravel relies on the table name, not the model name.
This should not be a problem in most cases because the pivot table is just an intermediate table and does not need a model unless you want to customize it.
The solution is straightforward. Tell Eloquent about the PostTag model:
# Filename: app/Models/Post.php
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)->using(PostTag::class);
}
The using method specifies the custom pivot model for the relationship.
Hit the same route again, and you will see a new logged entry:
[2021-04-03 12:59:46] local.DEBUG: {"tag_id":59,"post_id":1}
Summary
- Pivot tables are not observed by default -- Laravel does not fire model events for pivot tables because they do not have associated models out of the box.
- The pivot model must extend
Pivot, notModel-- using the wrong base class causes afromRawAttributeserror. - The
using()method connects the pivot model to the relationship -- without it, Eloquent has no way of knowing your custom pivot model exists, and the observer will never fire.