Лучший способ реализации Слушателя и События

События и Слушатели

Работая с Laravel, мы часто забываем, что во фреймворке всё — это класс, и его можно использовать как POPO (Plain Old PHP Object — Старый добрый PHP-объект). Это мне значительно облегчило мою жизнь при работе с Событиями (Events) и слушателями (Listeners).

Пример

Предположим, у нас есть эти три события, которые должны отправлять электронные письма: PostApproved, PostRejected, PostSubmittedForReview.

Как мы обычно делаем? Запускаем эти события и прикрепляем к ним слушателей.

$listen = [
    PostApproved::class => [
        SendPostApprovedEmail::class
    ],
    PostRejected::class => [
        SendPostRejectedEmail::class
    ],
    PostSubmittedForReview::class => [
        SendPostSubmittedEmail::class
    ]
];

Нам нужно было создать трех разных слушателей для трех разных писем.

В слушателе мы делаем

class SendPostApprovedEmail {
    public function handle(PostApproved $event) {
        Mail::send(
            $event->user->email, 
            'emails.posts.approved', 
            ['user' => $event->user, 'post' => $event->post]
        );    
    }
}

Выглядит довольно просто и кажется, что лучшего способа сделать это, нет. Ведь, буквально, одна строка кода.

Но это удобно до тех пор,  пока наши события, которые должны были отправить электронные письма, не начали расти. Нам приходилось писать одно и то же для всех событий и слушателей.

Решение

События, в основном, POPO и слушатели также, в основном, POPO. И мы отрефакторили событие в следующее

namespace App\Events\Posts;

use App\Events\Interfaces\EventWithEmails;
use App\Events\Interfaces\IsPostInvolved;
use App\Events\Interfaces\ShouldNotifyIntercomAsEvent;
use App\Models\Post;
use App\Services\Emails\Mails\Posts\Approved;
use Illuminate\Support\Collection;

class PostApproved extends AbstractPostEvent implements ShouldNotifyIntercomAsEvent, IsPostInvolved, EventWithEmails
{
    protected $emails;
    
    public function __construct(Post $post)
    {
        parent::__construct($post);
    }
    
    public function getIntercomEventName(): string
    {
        return 'post_approved';
    }
    
    public function getEmails(): Collection
    {
        $emails = collect();
        $approvedEmail = new Approved();
        $approvedEmail->sendTo($this->getAuthor());
        $approvedEmail->setParams([
            'user' => $this->getAuthor()->toArray(),
            'post' => $this->getPost()->toArray()
        ]);
        $emails->push($approvedEmail);
        return $emails;
    }
}

А этот слушатель стал

namespace App\Listeners;

use App\Events\Interfaces\EventWithEmails;
use App\Services\Emails\Mails\MailInterface;
use Illuminate\Contracts\Queue\ShouldQueue;

class EmailSender implements ShouldQueue
{
    public function handle(EventWithEmails $event)
    {
        $event->getEmails()->each(function (MailInterface $email){
            $email->send();
        });
    }
}

Это продакшн-код. Теперь нам не нужно добавлять класс каждый раз, когда возникает новое событие, которое должно инициировать электронную почту. Это дает гораздо больше преимуществ, чем размер кода. Это заставляет классы придерживаться принципа Открытости/Закрытости из SOLID.

Автор: Naren Chitrakar
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.


Задать вопросы по урокам можно на нашем форуме.