If you have Watcher events to update or delete records, it's important to know that they only run when individual records are updated, not when they are updated in
bulk.Observer Code:This Observer is registered with the Service Provider:Now imagine three different Eloquent queries:
app/Observers/PostObserver.php
:
class PostObserver{ public function deleted(Post $post) { // Например, удаляет связанные файлы изображений }}
app/Providers/AppServiceProvider.php
:
use App\Models\Post;use App\Observers\PostObserver;class AppServiceProvider extends ServiceProvider{ public function boot() { Post::observe(PostObserver::class); }}
$post = Post::first();$post->delete();// Это БУДЕТ запускать НаблюдателяPost::find(2)->delete();// Это также БУДЕТ запускать НаблюдателяPost::where('id', '>', 3)->delete();// Но это НЕ БУДЕТ запускать Наблюдателя!$user->posts()->delete();// Это также НЕ БУДЕТ запускать Наблюдателя!
The reason is that the Observer events originate from the Eloquent Model. In the case delete()
of Query Builder, the query is executed directly to the database, bypassing the individual model and its events.
Even if the result of the query is a single Eloquent model, Watchers will not run.
Post::where('id', 4)->delete();
This is true for any Observer method: updated()
either updating()
, delete()
or deleting()
.
This is just as important in the case of some packets that automatically register Observers.
For example, in the case of Spatie Media Library, it registers a method deleted()
that will not delete the associated media files if you delete Model entries in bulk instead of 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()); }); }