Icanup
0% прочитано

Я додала кілька EventServiceProvider у Laravel і один listener зареєструвався тричі

Під час підключення модульної події я випадково помітила, що Laravel тричі зареєстрував стандартний listener підтвердження email. Причина виявилася не в кеші, а в успадкуванні кількох модульних provider-ів від EventServiceProvider.

23 липня 2026 р. 6 хв читанняЛаравель

Як я помітила дублювання listener

Під час підключення нотифікацій після реєстрації користувача я додала окрему подію UserRegistered і зареєструвала для неї listener у модулі Notification. Звичайна перевірка через php artisan event:list несподівано показала іншу проблему: стандартний Laravel listener для підтвердження email був зареєстрований не один, а три рази.

Після реєстрації власної події я перевірила список подій і listeners:

bash
php artisan event:list

Власна подія була підключена правильно:

text
App\Modules\Auth\Domain\Event\UserRegistered└── App\Modules\Notification\Application\Listener\    DispatchNotificationsAfterUserRegistrationListener

Але нижче я побачила три однакові listeners для стандартної Laravel-події Registered:

text
Illuminate\Auth\Events\Registered├── Illuminate\Auth\Listeners\SendEmailVerificationNotification├── Illuminate\Auth\Listeners\SendEmailVerificationNotification└── Illuminate\Auth\Listeners\SendEmailVerificationNotification
Якби подія Registered була відправлена в такому стані, verification email потенційно міг оброблятися кілька разів.

Перша підозра - event cache

Першою думкою був застарілий кеш подій. Я очистила event cache і всі bootstrap caches:

bash
php artisan event:clearphp artisan optimize:clear php artisan event:list \    | grep -A 4 "Illuminate\\\\Auth\\\\Events\\\\Registered"

Але результат не змінився. SendEmailVerificationNotification усе ще був зареєстрований тричі.

Це означало, що проблема виникала під час завантаження застосунку, а не через старий кеш.

Звідки взялася повторна реєстрація

У bootstrap/providers.php у мене були зареєстровані два модульні event providers:

php
return [    App\Providers\AppServiceProvider::class,     App\Modules\Media\Provider\MediaEventProvider::class,    App\Modules\Notification\Provider\NotificationEventServiceProvider::class,];

Обидва класи наслідували стандартний Laravel EventServiceProvider:

php
use Illuminate\Foundation\Support\Providers\EventServiceProvider; final class MediaEventProvider extends EventServiceProvider{    protected $listen = [        // Media events...    ];}
php
use Illuminate\Foundation\Support\Providers\EventServiceProvider; final class NotificationEventServiceProvider extends EventServiceProvider{    protected $listen = [        UserRegistered::class => [            DispatchNotificationsAfterUserRegistrationListener::class,        ],    ];}

Чому дублювався саме listener підтвердження email

Проблема була не в масивах $listen.

Кожен модульний provider успадковував додаткову системну поведінку Laravel EventServiceProvider. Під час реєстрації такого provider Laravel також налаштовував стандартний listener для email verification.

Спрощено ця поведінка виглядала так:

php
Registered::class    => SendEmailVerificationNotification::class;

У результаті listener реєструвався:

  1. стандартною конфігурацією Laravel;

  2. під час завантаження MediaEventProvider;

  3. під час завантаження NotificationEventServiceProvider.

Саме тому event:list показував три однакові записи.

Окремий EventServiceProvider для модулів

Я створила базовий provider для модульних подій. Він зберігає можливість використовувати $listen, але вимикає повторне налаштування глобального verification listener.
php
<?php declare(strict_types=1); namespace App\Modules\Core\Provider; use Illuminate\Foundation\Support\Providers\EventServiceProvider; abstract class ModuleEventServiceProvider extends EventServiceProvider{    /**     * Module providers must not register Laravel's     * global email verification listener.     */    protected function configureEmailVerification(): void    {        // Intentionally left blank.    }}

Після цього модульні event providers почали наслідувати новий базовий клас.

php
use App\Modules\Core\Provider\ModuleEventServiceProvider; final class MediaEventProvider extends ModuleEventServiceProvider{    protected $listen = [        MediaFolderItemsChangedEvent::class => [            SyncMediaFolderItemsCountListener::class,        ],         MediaUsageChangedEvent::class => [            SyncMediaUsageCountListener::class,        ],    ];}
php
use App\Modules\Core\Provider\ModuleEventServiceProvider; final class NotificationEventServiceProvider extends ModuleEventServiceProvider{    protected $listen = [        UserRegistered::class => [            DispatchNotificationsAfterUserRegistrationListener::class,        ],    ];}

Результат до і після виправлення

listener-three-times-fix

Після зміни успадкування я ще раз очистила кеші та перевірила список подій:

bash
php artisan optimize:clear php artisan event:list \    | grep -A 4 "Illuminate\\\\Auth\\\\Events\\\\Registered"

Чи потрібно змінювати всі service providers

Ні. Проблема стосувалася лише модульних класів, які наслідували Laravel EventServiceProvider.

Звичайний ServiceProvider не виконує автоматичне налаштування email verification, тому такі providers змінювати не потрібно.

php
use Illuminate\Support\ServiceProvider; final class NotificationServiceProvider extends ServiceProvider{    public function boot(): void    {        Notification::observe(NotificationObserver::class);    }}

Висновок

Це один із тих випадків, коли проста діагностична команда виявляється кориснішою за довгий пошук у коді. Я перевіряла новий UserRegistered listener, а в результаті знайшла причину, через яку стандартне підтвердження email могло запускатися кілька разів.