- Опубликовано: 30 мар 2025
- 33
Laravel Observers и ShouldHandleEventsAfterCommit: зачем и как использовать
Когда вы работаете с Laravel и его Eloquent-моделями, нередко возникает желание автоматически выполнять определённые действия после создания, обновления или удаления записей в базе данных. Именно для этого в Laravel существуют Observers.
Однако, как только в игру вступают транзакции, появляется риск: обработчики в обсервере могут сработать до того, как изменения будут окончательно сохранены в базе. Это может привести к различным багам и побочным эффектам.
В этой статье я разберу:
- Что такое транзакции в Laravel
- Как работают observers
- Почему важно использовать ShouldHandleEventsAfterCommit
- Жизненные примеры с багами и их исправлением
Что такое транзакция в Laravel?
Транзакция — это механизм, позволяющий выполнять несколько запросов к базе данных как одну операцию. Если хотя бы один из запросов завершится с ошибкой, все изменения откатываются.
Пример:
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
$user = User::create([...]);
$user->profile()->create([...]);
// Ошибка, всё откатится
throw new \Exception('Ошибка!');
});
В этом коде создаётся пользователь и его профиль. Но если происходит ошибка, база данных откатит оба действия. Это удобно и безопасно — если только никакие побочные эффекты не произошли раньше времени.
Как работают Observers
Observers — это классы, которые реагируют на события модели: created
, updated
, deleted
и т.д.
Пример:
class UserObserver
{
public function created(User $user): void
{
Mail::to($user->email)->send(new WelcomeMail($user));
}
}
Такой обработчик автоматически отправит письмо новому пользователю после создания записи в базе.
Проблема: Observer срабатывает до завершения транзакции
Если вы создаёте пользователя внутри DB::transaction(...)
, Laravel по умолчанию немедленно вызывает created()
, даже если транзакция ещё не завершена.
Пример бага:
DB::transaction(function () {
User::create([ 'email' => 'test@example.com' ]);
throw new \Exception('Ошибка!');
});
Если в UserObserver
прописано:
public function created(User $user): void
{
Mail::to($user->email)->send(...);
}
То письмо уже отправится, хотя транзакция будет откатана и пользователь в базу не попадёт.
Решение: ShouldHandleEventsAfterCommit
Laravel позволяет "задерживать" вызов обработчиков до завершения транзакции с помощью интерфейса:
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class UserObserver implements ShouldHandleEventsAfterCommit
{
public function created(User $user): void
{
// Этот код выполнится только после успешного commit'а
}
}
Теперь, если транзакция откатится — created()
не будет вызван вовсе. Если commit прошёл — вызов произойдёт сразу после него.
Жизненные примеры использования
1. Создание связанных моделей
public function created(User $user): void
{
$user->profile()->create([...]);
}
Без интерфейса: профиль может быть создан, а пользователь — нет (если транзакция откатится).
С интерфейсом: создаётся только если всё успешно.
2. Аудит и логирование
AuditLog::create([
'action' => 'created user',
'user_id' => $user->id,
]);
Без интерфейса: в логе будет запись о пользователе, которого нет.
3. Уведомления и письма
Notification::send($user, new WelcomeNotification());
Без интерфейса: уведомление отправится, даже если пользователь не сохранён.
Когда стоит использовать ShouldHandleEventsAfterCommit?
Рекомендуется использовать его, если в обработчике:
- Отправляются письма или уведомления
- Создаются связанные модели
- Пишется лог/аудит
- Обновляются внешние API, кеши, очереди
Вывод
ShouldHandleEventsAfterCommit
— это мощный инструмент для корректной работы ваших Observer'ов в Laravel, особенно при использовании транзакций. Он помогает избежать побочных эффектов и багов, которые тяжело отследить.
Если вы ещё не используете этот интерфейс — самое время внедрить его в те Observer'ы, где есть внешние действия.
Была статья полезной: