Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді: Events

Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів

3 питання

Події дають слабке зв'язування: одна частина застосунку «оголошує», що щось сталося, інші - реагують, нічого не знаючи одна про одну.

event(new OrderShipped($order)); // диспатч

// слухач
class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        // ...
    }
}
  • Слухача, що реалізує ShouldQueue, обробляють асинхронно в черзі.
  • У сучасному Laravel слухачі автоматично виявляються за type-hint у методі handle - ручна реєстрація не обов'язкова.

Приклад: подія UserRegistered → слухачі «надіслати лист», «нарахувати бонус», «оновити статистику».

Докладніше в документації: Events

Подія повідомляє, що щось сталося, не знаючи, хто на це зреагує. Це розвʼязує звʼязок між тим, хто діє, і тим, хто відповідає.

OrderPlaced::dispatch($order);

Слухачі - лист, нарахування бонусів, повідомлення на склад - підписані окремо, і додати ще одного можна, не чіпаючи оформлення замовлення.

Коли події доречні:

  • На одну дію треба кілька незалежних реакцій, і їхній перелік з часом росте.
  • Реакції належать іншим доменам - розсилка, аналітика, інтеграції.
  • Реакція має піти у фонову роботу (ShouldQueue на слухачі).
  • Реагувати мусить пакет чи модуль, який про ваш код не знає.

Коли краще виклик напряму:

  • Реакція єдина й завжди та сама. OrderPlaced з одним слухачем - це виклик методу, записаний у два файли.
  • Порядок і результат важливі: подія не повертає значення й не гарантує послідовності.
  • Дія - частина тієї самої операції, яка мусить відкотитися разом із нею.

Головна пастка - невидимість. Через рік ніхто не скаже, що саме відбувається під час оформлення замовлення: у коді видно один рядок, а реально виконуються сім слухачів у різних файлах. Налагодження зводиться до пошуку по всьому проєкту.

Друга пастка - транзакції. Подія, відправлена всередині DB::transaction(), може дійти до черги раніше, ніж транзакція завершиться, - і воркер не знайде запису. Для цього є ShouldDispatchAfterCommit на події або afterCommit на завданні.

Правило: починайте з прямого виклику. Переходьте на подію, коли реакцій стало більше однієї або вони належать іншій частині системи.

Докладніше в документації: Події

Eloquent сам розсилає події життєвого циклу: creating, created, updating, updated, saving, saved, deleting, deleted, restored, replicating.

Зручно для дрібниць, які мають виконуватися завжди:

class Post extends Model
{
    protected static function booted(): void
    {
        static::creating(function (Post $post) {
            $post->slug ??= Str::slug($post->title);
        });
    }
}

Для повного набору краще окремий observer - інакше booted() розростається:

#[ObservedBy(PostObserver::class)]
class Post extends Model
{
}

Чим небезпечні:

  • Масові операції їх не викликають. Post::where(...)->update([...]), ->delete() та insert() йдуть повз Eloquent, тож жоден слухач не спрацює. Дані тихо розходяться з тим, що гарантував observer.
  • Логіка стає невидимою. $post->save() у контролері виглядає простим рядком, а насправді тягне за собою запис в інші таблиці й виклик API.
  • Тести сповільнюються. Кожна фабрика в кожному тесті тягне весь ланцюжок подій, включно з тими, що ходять у мережу.
  • Помилка в слухачі ламає збереження. Виняток у creating скасовує операцію - зокрема там, де про це ніхто не думав.
  • Рекурсія. Слухач saved, який оновлює ту саму модель, викликає сам себе; рятує saveQuietly().

Правило: модельні події - для інваріантів самої моделі (слаг, нормалізація поля, службові звʼязки). Бізнес-процес - оформлення замовлення, розсилка, інтеграції - належить явному сервісу чи доменній події, які видно в місці виклику.

Докладніше в документації: Eloquent