• Czas czytania ~8 min
  • 21.02.2023

W większości aplikacji musimy wysyłać powiadomienia, zarówno w aplikacji, e-mail, jak i slack - są to zazwyczaj powiadomienia transakcyjne, aby ostrzec użytkowników o jakiejś akcji lub zdarzeniu w aplikacji. Zanurzmy się.

Pierwszą rzeczą, o której musisz pomyśleć, gdy chcesz wysyłać powiadomienia do użytkownika, jest mechanizm dostarczania. Czy chcemy wysłać szybką wiadomość e-mail? A może jest to powiadomienie w stylu systemowym, w którym chcemy wysłać dane do kanału slack? A może jest to powiadomienie w aplikacji, ponieważ komuś spodobało się zdjęcie Twojego kota?

Tak czy inaczej, dostawa jest podstawą każdego powiadomienia, a dzięki Laravel możesz dostarczać do wielu kanałów z jednego powiadomienia. Robimy to za pomocą via metody samej klasy Notification, określając tablicę kanałów, na które chcemy wysłać nasze powiadomienie.

Po wyjęciu z pudełka możesz wysyłać slack, e-maile i w aplikacji bez żadnych dodatkowych pakietów. Jednak w dzisiejszych czasach istnieje wiele opcji, jeśli chodzi o kanały powiadomień. Na szczęście witryna utrzymywana przez społeczność pokazuje opcje, instrukcje instalacji i sposób wysyłania powiadomień za pośrednictwem wielu różnych kanałów.

Przyjrzyjmy się podstawowemu przykładowi wysyłania powiadomienia. Mamy system, w którym ludzie mogą rezerwować spotkania, myśleć o Calendly lub SavvyCal. Niezależnie od tego, czy jest to API, interakcja internetowa, czy nawet interfejs wiersza polecenia, zawsze będziemy chcieli uzyskać ten sam wynik. Jeśli ktoś zarezerwuje spotkanie, powinniśmy powiadomić tę osobę, informując ją, że spotkanie zostało zarezerwowane, z dołączonym zaproszeniem do kalendarza. Chcemy również poinformować o tym osobę, z którą zarezerwowane jest spotkanie, aby mogła być świadoma. Możemy również chcieć zaktualizować coś w systemie innej firmy, aby zarządzać dostępnością.

Po pierwsze, wygenerujmy powiadomienie za pomocą naszej konsoli rzemieślniczej:

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.

Po wygenerowaniu kodu powiadomienia powinniśmy skupić się na tym, jakie właściwości muszą zostać przekazane do konstruktora (jeśli w ogóle) i do jakich kanałów chcemy dostarczyć. Załóżmy więc, że chcemy zaakceptować nazwę i adres e-mail do powiadomienia, abyśmy mogli powiadomić użytkownika, który zarezerwował z nim spotkanie.

Zacznijmy budować nasze powiadomienie:

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.

Użyjemy tego powiadomienia, aby wysłać wiadomość e-mail do użytkownika, aby poinformować go, że ma zarezerwowane spotkanie. Aby to zrobić, musimy dodać via metodę i stwierdzić, że chcemy korzystać z kanału 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.

Powiedzmy, że musimy teraz dodać to do luźnego kanału, być może ze względu na śledzenie. Nadal chcemy wysyłać powiadomienia e-mail, ale dodać kanał.

Na początek powinniśmy koniecznie przetestować to, co już zbudowaliśmy. Podobnie jak w przypadku wszystkich aspektów twoich projektów, nie możesz zagwarantować, że wszystko będzie działać, chyba że je przetestujesz. Najpierw napiszmy nasz test dotyczący wysyłania powiadomienia e-mail. Wtedy możemy wprowadzić nasze zmiany.

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.

Możemy dodać kolejną kontrolę w naszym teście tutaj, aby upewnić się, że nasz test jest wysyłany do właściwego kanału:

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.

Teraz jesteśmy całkiem zadowoleni z naszego zasięgu testowego. Wiemy, że gdy akcja zostanie obsłużona, powiadomienie zostanie wysłane, a gdy zostanie wysłane, kanały obejmują kanał pocztowy - co oznacza, że nasz e-mail zostanie dostarczony.

Wróćmy do dodawania kolejnego kanału: slack. Ukryłem toMail metodę z poniższego przykładu kodu, aby łatwiej było zobaczyć, co jest robione:

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.

Jedną rzeczą do zapamiętania jest to, że powiadomienia nie muszą być kolejkowane. Mogą one być również wysyłane synchronicznie.

Teraz wiemy, jak utworzyć i przetestować powiadomienie; Spójrzmy na ich wysłanie.

W domyślnej aplikacji Laravel model użytkownika implementuje cechę Notifiable , która pozwala na wykonanie czegoś takiego:

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.

Zaczyna się od fasady powiadomienia. Następnie przekazujesz mu route powiadomienie z kanałem i argumentem.

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.

Jak korzystać z powiadomień w aplikacjach Laravel? Jaki jest Twój ulubiony kanał? Daj nam znać na Twitterze!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297