Reminder: Eloquent Observers Are Not Fired For Mass-Update or Mass-Delete

Eloquent Observer executed

If you have Observer events on updated/deleted rows, it's important to know that they are fired only when you update individual rows, and not when doing mass-update or mass-delete.

The code of the Observer:

app/Observers/PostObserver.php:

class PostObserver
{
public function deleted(Post $post)
{
// For example, delete the related image files
}
}

This Observer is registered in the Service Provider:

app/Providers/AppServiceProvider.php:

use App\Models\Post;
use App\Observers\PostObserver;
 
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Post::observe(PostObserver::class);
}
}

And then imagine three different Eloquent queries:

$post = Post::first();
$post->delete();
// This WILL execute the observer
 
Post::find(2)->delete();
// This also WILL execute the observer
 
Post::where('id', '>', 3)->delete();
// But this will NOT execute the observer!
 
$user->posts()->delete();
// This will also NOT execute the observer!

The reason is that those Observer events come from Eloquent Model. In the case of delete() from Query Builder, it executes the query directly to the database, bypassing the individual model and its events.

Even if the result of the Query is one single Eloquent model, the observers will not be fired.

Post::where('id', 4)->delete();

This is true for any Observer method, like updated() or updating(), delete() or deleting().


It is also important in the case of some packages that automatically register the Observers.

Like, in the case of Spatie Media Library, it registers the deleting() method which will not delete the associated Media files, if you are mass-deleting the related Model rows and not deleting a single Model.

laravel-medialibrary/src/InteractsWithMedia.php:

trait InteractsWithMedia
{
public static function bootInteractsWithMedia()
{
static::deleting(function (HasMedia $model) {
if ($model->shouldDeletePreservingMedia()) {
return;
}
 
if (in_array(SoftDeletes::class, class_uses_recursive($model))) {
if (! $model->forceDeleting) {
return;
}
}
 
$model->media()->cursor()->each(fn (Media $media) => $media->delete());
});
}

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1056 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials