Laravel: Eloquent Наблюдатели не выполняются при массовых событиях
Код Наблюдателя:
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());
});
}