Laravel: Eloquent Наблюдатели не выполняются при массовых событиях

Источник: «Reminder: Eloquent Observers Are Not Fired For Mass-Update or Mass-Delete»
Если у вас есть события Наблюдателя для обновления или удаления записей, важно знать, что они выполняются только при обновлении отдельных записей, а не при массовом обновлении или удалении.

Код Наблюдателя:

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());
});
}

Дополнительные материалы

Предыдущая Статья

Laravel: Как передать глобальные переменные в Blade.

Следующая Статья

Как предотвратить XSS атаку. Два уровня защиты