Если у вас есть события Наблюдателя для обновления или удаления записей, важно знать, что они выполняются только при обновлении отдельных записей, а не при массовом обновлении или удалении.
Код Наблюдателя:
app/Observers/PostObserver.php
:
class PostObserver{ public function deleted(Post $post) { // Например, удаляет связанные файлы изображений }}
Этот Наблюдатель зарегистрирован в 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); }}
А теперь представьте три разных Eloquent запроса:
$post = Post::first();$post->delete();// Это БУДЕТ запускать НаблюдателяPost::find(2)->delete();// Это также БУДЕТ запускать НаблюдателяPost::where('id', '>', 3)->delete();// Но это НЕ БУДЕТ запускать Наблюдателя!$user->posts()->delete();// Это также НЕ БУДЕТ запускать Наблюдателя!
Причина в том, что события Наблюдателя происходят из Eloquent Модели. В случае delete()
из Query Builder запрос выполняется непосредственно к базе данных, минуя отдельную модель и её события.
Даже если результатом запроса является одна единственная модель Eloquent, Наблюдатели не будут запущены.
Post::where('id', 4)->delete();
Это верно для любого метода Наблюдателя: updated()
или updating()
, delete()
или deleting()
.
Это так же важно в случае некоторых пакетов, которые автоматически регистрируют Наблюдателей.
Например, в случае Spatie Media Library она регистрирует метод deleted()
, который не удалит связанный медиа файлы, если массово удалить записи Модели, а не одну Модель.
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()); }); }