Laravel. События, слушатели и подписчики

25.11.2020

Теги: LaravelPHPWeb-разработкаКлассСобытиеФреймворк

События в Laravel представлены реализацией паттерна Observer, что позволяет подписываться и прослушивать события приложения. Как правило, классы событий находятся в директории app/Events, а классы обработчиков событий — в app/Listeners.

Создание класса события

Создаем класс события с помощью artisan-команды:

> php artisan make:event CreatePageEvent
namespace App\Events;

use App\Models\Page;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class CreatePageEvent {

    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $page;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Page $page) {
        $this->page = $page;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn() {
        return [];
    }
}

Как видите, этот класс события не содержит логики — это просто контейнер для объекта Page. Трейт SerializesModels необходим, чтобы корректно сериализовать (представлять в виде строки) Eloquent-модели.

Создание обработчика события

Создаем обработчик события с помощью artisan-команды:

> php artisan make:listener CreatePageListener --event="CreatePageEvent"
namespace App\Listeners;

use App\Events\CreatePageEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class CreatePageListener {
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct() {
        // ...
    }

    /**
     * Handle the event.
     *
     * @param  CreatePageEvent  $event
     * @return void
     */
    public function handle(CreatePageEvent $event) {
        // обрабатываем событие создания новой страницы
        $event->page->name .= ' (автор ' . auth()->user()->name . ')';
        $event->page->save();
    }
}

Сразу после добавления новой страницы в базу данных мы добавляем к названию страницы автора и еще раз сохраняем модель.

Чтобы остановить распространение события для других слушателей — нужно вернуть false из метода handle().

Регистрация слушателя события

Открываем на редактирование файл класса EventServiceProvider:

namespace App\Providers;

use App\Events\CreatePageEvent;
use App\Listeners\CreatePageListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider {
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        // обработчик события создания новой страницы
        CreatePageEvent::class => [
            CreatePageListener::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot() {
        parent::boot();
        // ...
    }
}

Возбуждение события

Теперь в ресурсном контроллере, который отвечает за создание, редактирование и удаление страниц сайта, возбудим событие:

namespace App\Http\Controllers\Admin;

use App\Events\CreatePageEvent;
use App\Http\Controllers\Controller;
use App\Models\Page;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PageController extends Controller {
    /**
     * Показывает список всех страниц
     */
    public function index() {
        $pages = Page::all();
        return view('admin.page.index', compact('pages'));
    }

    /**
     * Показывает форму для создания страницы
     */
    public function create() {
        return view('admin.page.create');
    }

    /**
     * Сохраняет новую страницу в базу данных
     */
    public function store(Request $request) {
        $page = Page::create($request->all());
        /*
         * возбуждаем событие создания страницы
         */
        event(new CreatePageEvent($page));
        return redirect()->route('admin.page.show', ['page' => $page->id]);
    }

    /**
     * Показывает информацию о странице сайта
     */
    public function show(Page $page) {
        return view('admin.page.show', compact('page'));
    }

    /**
     * Показывает форму для редактирования страницы
     */
    public function edit(Page $page) {
        return view('admin.page.edit', compact('page'));
    }

    /**
     * Обновляет страницу (запись в таблице БД)
     */
    public function update(Request $request, Page $page) {
        $page->update($request->all());
        return redirect()->route('admin.page.show', ['page' => $page->id]);
    }

    /**
     * Удаляет страницу (запись в таблице БД)
     */
    public function destroy(Page $page) {
        $page->delete();
        return redirect()->route('admin.page.index');
    }
}

Упрощенный вариант

Если слушателей событий не много и их код не сложный, можно не создавать отдельные классы для события и его слушателя, а использовать метод boot() сервис-провайдера EventServiceProvider.

// возбуждаем событие создания страницы
event('CreatePageEvent', $page);
namespace App\Providers;

use App\Models\Page;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider {
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot() {
        parent::boot();
        Event::listen('CreatePageEvent', function (Page $page) {
            // обрабатываем событие создания новой страницы
            $page->name .= ' (автор ' . auth()->user()->name . ')';
            $page->save();
        });
    }
}

Подписчик на события

Подписчик на события — это класс, который подписывается на несколько событий и сам эти события обрабатывает. Такой класс удобно использовать, если есть ряд однотипных событий — например, действия связанные с моделью Page (добавление, редактирование, удаление).

namespace App\Listeners;

use App\Models\Page;

class PageEventSubscriber {
    /**
     * Обработка события после создания страницы
     */
    public function handleAfterCreatePage(Page $page) {
        $page->name .= ' [created:' . now('Europe/Moscow') . ']';
        $page->save();
    }

    /**
     * Обработка события после обновления страницы
     */
    public function handleAfterUpdatePage(Page $page) {
        $page->name .= ' [updated:' . now('Europe/Moscow') . ']';
        $page->save();
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events) {
        $events->listen(
            'AfterCreatePageEvent',
            'App\Listeners\PageEventSubscriber@handleAfterCreatePage'
        );
        $events->listen(
            'AfterUpdatePageEvent',
            'App\Listeners\PageEventSubscriber@handleAfterUpdatePage'
        );
    }
}

Данный класс подписан на два события (создавать их классы отдельно не нужно) — AfterCreatePageEvent и AfterUpdatePageEvent — которые передаются первым аргументом метода listen(). В качестве второго аргумента передается класс и метод, обрабатывающий каждое из этих событий.

Вместо создания методов для обработки каждого события, можно передать вторым аргументом метода listen() функцию-замыкание, которая и выполнит обработку события.

Этот класс-подписчик нужно зарегистрировать в сервис-провайдере EventServiceProvider:

class EventServiceProvider extends ServiceProvider {
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        // ...
    ];

    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\PageEventSubscriber',
    ];
    /* ... */
}

Кроме того, подписать можно в любом месте кода до момента возбуждения события:

Event::subscribe(new App\Listeners\PageEventSubscriber());

Возбуждение события осуществляется хелпером event():

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Page;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class PageController extends Controller {
    /**
     * Показывает список всех страниц
     */
    public function index() {
        $pages = Page::all();
        return view('admin.page.index', compact('pages'));
    }

    /**
     * Показывает форму для создания страницы
     */
    public function create() {
        return view('admin.page.create');
    }

    /**
     * Сохраняет новую страницу в базу данных
     */
    public function store(Request $request) {
        $page = Page::create($request->all());
        /*
         * возбуждаем событие после создания страницы
         */
        event('AfterCreatePageEvent', $page);
        return redirect()->route('admin.page.show', ['page' => $page->id]);
    }

    /**
     * Показывает информацию о странице сайта
     */
    public function show(Page $page) {
        return view('admin.page.show', compact('page'));
    }

    /**
     * Показывает форму для редактирования страницы
     */
    public function edit(Page $page) {
        return view('admin.page.edit', compact('page'));
    }

    /**
     * Обновляет страницу (запись в таблице БД)
     */
    public function update(Request $request, Page $page) {
        $page->update($request->all());
        /*
         * возбуждаем событие после обновления страницы
         */
        event('AfterCreatePageEvent', $page);
        return redirect()->route('admin.page.show', ['page' => $page->id]);
    }

    /**
     * Удаляет страницу (запись в таблице БД)
     */
    public function destroy(Page $page) {
        $page->delete();
        return redirect()->route('admin.page.index');
    }
}

Собственно, между Слушателем (Listener) и Подписчиком (Subscriber) нет особого отличия. Просто Слушатель может обрабатывать только одно событие в методе handle(), а Подписчик — сразу несколько событий в методах handleOneEvent(), handleTwoEvent().

События Eloquent

Список предопределенных событий, которые можно использовать при работе с Eloquent моделями, можно посмотреть в трейте HasEvents. Это retrieved — при извлечении модели из базы данных, creating — перед записью новой модели в базу данных, created — после записи новой модели в базу данных и так далее.

namespace Illuminate\Database\Eloquent\Concerns;

use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Events\NullDispatcher;
use Illuminate\Support\Arr;
use InvalidArgumentException;

trait HasEvents {
    /**
     * Get the observable event names.
     *
     * @return array
     */
    public function getObservableEvents() {
        return array_merge(
            [
                'retrieved', 'creating', 'created', 'updating', 'updated',
                'saving', 'saved', 'restoring', 'restored', 'replicating',
                'deleting', 'deleted', 'forceDeleted',
            ],
            $this->observables
        );
    }
    /* ... */
}

Подробнее об этом можно прочитать здесь.

Поиск: Laravel • PHP • Web-разработка • Класс • Событие • Фреймворк • Event • Listener • Observer • Наблюдатель • Слушатель

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.