• Время чтения ~8 мин
  • 21.02.2023

В большинстве приложений нам нужно отправлять уведомления, будь то в приложении, по электронной почте или в Slack - это, как правило, транзакционные уведомления, чтобы предупредить пользователей о каком-либо действии или событии в вашем приложении. Давайте погрузимся.

Первое, о чем нужно подумать, когда вы хотите отправлять уведомления пользователю, это механизм доставки. Хотим ли мы отправить быстрое электронное письмо? Или это уведомление в системном стиле, когда мы хотим отправить данные в канал slack? Или, может быть, это уведомление в приложении, потому что кому-то понравилась фотография вашей кошки?

В любом случае, доставка лежит в основе любого уведомления, и, конечно же, с Laravel вы можете доставлять по нескольким каналам из одного уведомления. Мы делаем это с помощью via метода в самом классе Notification, указывая массив каналов, по которым мы хотим отправить наше уведомление.

Из коробки вы можете отправлять slack, электронную почту и в приложении без каких-либо дополнительных пакетов. Тем не менее, в наши дни есть много вариантов, когда дело доходит до каналов уведомлений. К счастью, веб-сайт, поддерживаемый сообществом , показывает вам варианты, инструкции по установке и как отправлять уведомления по различным каналам.

Давайте рассмотрим базовый пример отправки уведомления. У нас есть система, где люди могут бронировать встречи, думать Calendly или SavvyCal. Будь то API, веб-взаимодействие или даже CLI, мы всегда будем хотеть одного и того же результата. Если кто-то заказывает собрание, мы должны уведомить этого человека, сообщив ему, что собрание было забронировано, с прикрепленным календарем. Мы также хотим, чтобы человек, с которым забронирована встреча, знал, чтобы он мог быть в курсе. Мы также можем обновить что-то в сторонней системе для управления доступностью.

Во-первых, давайте сгенерируем уведомление с помощью нашей ремесленной консоли:

php artisan make:notification UserMeetingBookedNotification --test

Let's break down the command. We want to make a notification that part is clear and fits most artisan commands when generating code. We then provide a name for the Notification itself. You could use namespaces to further the grouping of the notification using either "\" or "/" to separate namespaces. We also tag on the option --test so that Laravel will generate a test for this specific notification; this will be a Feature test. I use pestPHP for my testing framework, so I usually publish the stubs to customize the generated test output.

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

Давайте начнем создавать наше уведомление:

declare(strict_types=1);

namespace App\Notifications;

use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

final class UserMeetingBookedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly CarbonInterface $datetime,
    ) {}
}

We can now build our notification to deliver. We need to decide on a channel. For this tutorial, I will focus on the options available by default, but Laravel Notification Channels has a lot of information you can use for alternative channels.

Мы будем использовать это уведомление, чтобы отправить пользователю электронное письмо, чтобы сообщить ему, что у него забронирована встреча. Поэтому для этого нам нужно добавить via метод и заявить, что мы хотим использовать mail канал.

declare(strict_types=1);

namespace App\Notifications;

use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

final class UserMeetingBookedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly CarbonInterface $datetime,
    ) {}
    public function via(mixed $notifiable): array
    {
        return ['mail'];
    }
}

Once we have added the via method, we need to describe the Mail Message we will be sending. We do this in the toMail method. The general rule here is that each channel requires a to method, where we prefix the capitalized channel with to.

declare(strict_types=1);

namespace App\Notifications;

use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

final class UserMeetingBookedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly CarbonInterface $datetime,
    ) {}
    public function via(mixed $notifiable): array
    {
        return ['mail'];
    }
    public function toMail(mixed $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('New Booking Received.')
            ->greeting('Booking received')
            ->line("{$this->name} has booked a meeting with you.")
            ->line("This has been booked for {$this->datetime->format('l jS \\of F Y h:i:s A')}")
            ->line("You can email them on {$this->email} if you need to organise anything.");
    }
}

That is it; this will send a nice simple email to the user alerting them how they can get in touch and the fact that they received a booking.

Предположим, что теперь нам нужно добавить это в канал slack, возможно, по причинам отслеживания. Мы по-прежнему хотим отправить уведомление по электронной почте, но добавить канал.

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

it('can send an email notification when a booking is made', function () {
    Notification::fake();

    $user = User::factory()->create();

    expect(
        Bookings::query()->count(),
    )->toEqual(0);

    $action = app()->make(
        abstract: CreateNewBookingAction::class,
    );
    $action->handle(
        user: $user->id,
        name: 'Test User',
        email: '[email protected]',
        time: now()->addHours(12),
    );
    expect(
        Bookings::query()->count(),
    )->toEqual(1);

    Notification::assertSentTo(
        [$user],
        UserMeetingBookedNotification::class,
    );
});

We are faking the notification driver in Laravel so that the notification isn't routed to anyone. We then create a user we want to notify and resolve our logic from the container - abstracting this logic makes testing easier. We want to make sure that when we make a booking, it is saved, but then we want to ensure that the notification was sent as expected, just never delivered.

Мы можем добавить еще одну проверку в наш тест здесь, чтобы убедиться, что наш тест отправляется на правильный канал:

it('sends the notification to the correct channels', function () {
    Notification::fake();

    $user = User::factory()->create();

    $action = app()->make(
        abstract: CreateNewBookingAction::class,
    );
    $action->handle(
        user: $user->id,
        name: 'Test User',
        email: '[email protected]',
        time: now()->addHours(12),
    );
    Notification::assertSentTo(
        [$user],
        UserMeetingBookedNotification::class,
        function (mixed $notification, array $channels): bool {
            return in_array('mail', $channels);
        },
    );
});

A similar test to our initial one, but this time we are adding a third argument to our assert sent to call, which has a callable where we want to ensure that we are sending to our mail channel.

Теперь мы очень довольны нашим тестовым покрытием. Мы знаем, что при обработке действия будет отправлено уведомление, и что при его отправке каналы включают почтовый канал, что означает, что наша электронная почта будет доставлена.

Вернемся к добавлению следующего канала: slack. Я скрыл toMail метод из приведенного ниже примера кода, чтобы было легче увидеть, что делается:

declare(strict_types=1);

namespace App\Notifications;

use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;

final class UserMeetingBookedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly CarbonInterface $datetime,
    ) {}
    public function via(mixed $notifiable): array
    {
        return ['mail', 'slack'];
    }
    public function toSlack(mixed $notifiable): SlackMessage
    {
        return (new SlackMessage)
            ->success()
            ->content("{$this->name} just booked a meeting for {$this->datetime->format('l jS \\of F Y h:i:s A')}.");
    }
}

For this to work correctly, however, we need to make sure we install a first-party notification package by the laravel team:

composer require laravel/slack-notification-channel

Now that this is done, we can amend our test to ensure that we are also sending it to the slack channel:

it('sends the notification to the correct channels', function () {
    Notification::fake();

    $user = User::factory()->create();

    $action = app()->make(
        abstract: CreateNewBookingAction::class,
    );
    $action->handle(
        user: $user->id,
        name: 'Test User',
        email: '[email protected]',
        time: now()->addHours(12),
    );
    Notification::assertSentTo(
        [$user],
        UserMeetingBookedNotification::class,
        function (mixed $notification, array $channels): bool {
            return in_array('mail', $channels)
                & in_array('slack', $channels);
        },
    );
});

There are more tests we can do with our Notifications, but this is an excellent place to start without overcomplicating it. We know that we will send the correct Notification at the right time to the right user on the right channels.

Одна вещь, которую следует помнить, это то, что уведомления не должны быть поставлены в очередь. Они также могут быть отправлены синхронно.

Итак, теперь мы знаем, как создать и протестировать уведомление; давайте посмотрим на их отправку.

В приложении Laravel по умолчанию ваша пользовательская модель реализует черту Notifiable , которая позволяет вам делать что-то вроде следующего:

auth()->user()->notify(new UserMeetingBookedNotification(
    name: $request->get('name'),
    email: $request->('email'),
));

This is great, but the notifiable trait relies on the model having an email property that is accessible. What do we do when our use case does not quite fit this approach? The Notifiable trait is then not of much use to us. This is where I find the Notification facade to be your best friend. You can use the Notification facade to manually route a notification instead of leaning on the trait itself. Let's look at an example:

Notification::send(['email address one', 'email address two'], new EmailNotification($arguments));

The above example will allow you to programmatically send a notification to an array of email addresses quickly and easily. A slightly cleaner way to do so would be to use the on-demand notifications. Let's say we want to be able to send a notification as part of an artisan command programmatically. You can provide an argument for the channel and programmatically select how to send the notification.

Она начинается с фасада Notification. Затем вы сообщаете об этом уведомлению route с каналом и аргументом.

Notification::route('slack', 'slack-webhook-url')
    ->notify(new SlackNotification($argument));

The Notification facade is very powerful and flexible, allowing you to send notifications on demand and test them quickly.

Как вы используете уведомления в приложениях Laravel? Какой ваш любимый канал? Дайте нам знать в Твиттере!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297