{{TOC}} В предыдущей статье я описал новую функцию Laravel ((https://laravel.ru/posts/686 Mailable)) и рассказал о некоторых важных изменениях в отправке писем в Laravel. Советую ознакомиться с ней, если вы ещё не прочитали. В двух словах: предыдущий почтовый синтаксис больше не использует "классические" почтовые замыкания, вместо этого создаются "отправляемые" (Mailable) классы для каждого отдельного письма - например, отправляемый класс "WelcomeNewUser" (Приветствие нового пользователя). В Laravel 5.3 появилась ещё одна возможность взаимодействия с пользователями: **Уведомления**. Представьте какое-нибудь сообщение, которое надо отправить пользователю, и при этом не важно //как// именно он его получит. Уведомление о сбросе пароля, или уведомление "Есть новые непросмотренные изменения", или "Кто-то добавил вас в друзья". Для них совсем не обязательно использовать почту, они легко могут быть отправлены по SMS, или уведомлением в ((https://slack.com/ Slack)), или всплывающим сообщением в приложении, или любым другим из множества способов. Система уведомлений Laravel 5.3 позволяет легко настроить отдельный класс для каждого уведомления (например, "WorkoutAssigned" - "назначена тренировка"), который описывает способы передачи пользователям одного и того же сообщения по разным каналам, а также, как выбрать один из этих каналов для конкретного пользователя. == Создаём своё первое уведомление == Как и всегда, мы используем Artisan-команду для создания нашего уведомления: %%(sh) php artisan make:notification WorkoutAssigned %% Команда создаст файл %%(t)app/Notifications/WorkoutAssigned.php%%, который будет выглядеть так: %% line('Знакомство с уведомлениями.') ->action('Действие уведомления', 'https://laravel.com') ->line('Спасибо, что пользуетесь нашим приложением!'); } /** * Получить представление уведомления в виде массива. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ // ]; } } %% == Анатомия уведомлений == Давайте посмотрим, что тут есть. Во-первых, конструктор, где мы будем внедрять необходимые данные. %% public function __construct() {} %% Во-вторых, у нас есть метод %%via()%%, позволяющий задать, через //какой// из возможных каналов передачи будет отправлен каждый конкретный экземпляр. Верните массив с типами уведомлений, и данное уведомление будет отправлено //всеми// доступными способами. %% public function via($notifiable) { return ['mail']; } %% Сейчас мы просто оставим один конкретный канал передачи, но поскольку это метод, вы можете программно определить используемые каналы. Например, позволив пользователям самим задавать способы доставки. Изначально в вашем новом уведомлении показано, как можно настроить конкретный способ доставки на примере метода %%toMail()%%. Ему передаётся "notifiable" ("уведомляемый"), которого мы скоро рассмотрим, и вы создаёте и возвращаете письмо. %% public function toMail($notifiable) { return (new MailMessage) ->line('Знакомство с уведомлениями.') ->action('Действие уведомления', 'https://laravel.com') ->line('Спасибо, что пользуетесь нашим приложением!'); } %% И наконец, метод %%toArray()%% - стандартный запасной вариант, на который будут ссылаться те каналы передачи, которые вы не настроили (например, отправка в базу данных). %% public function toArray($notifiable) { return []; } %% Давайте отредактируем этот класс, чтобы он больше подходил для нашего уведомления "Назначена тренировка": %% ... class WorkoutAssigned extends Notification { use Queueable; private $workout; public function __construct($workout) { $this->workout = $workout; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->line("Вам назначена новая тренировка!") ->action('Просмотреть тренировку', route('workouts.show', [$this->workout])) ->line("Давайте приступим!"); } public function toArray($notifiable) { return [ 'workout' => $this->workout->id ]; } } %% Итак, в конструкторе должен быть создан экземпляр тренировки, и мы сможем уведомить наших пользователей о том, какая тренировка им назначена. == Что такое Notifiable (Уведомляемый)? == Пока я говорил только об уведомлении пользователей. Но технически уведомляемой может быть любая модель Eloquent. Она должна просто импортировать типаж %%Illuminate\Notifications\Notifiable%%. Вам может понадобиться уведомить Группу, Команду, Список, или любую другую подходящую модель. Но помните, что определённые каналы передачи ожидают определённой информации об уведомляемом. Например, для уведомлений по почте у модели должно быть свойство "email", чтобы понимать, на какой адрес отправлять уведомление. Вы можете настроить, как маршрутизировать конкретную модель для конкретного способа доставки, добавив такой метод в свою модель: %% ... class Group { use Notifiable; public function routeNotificationForMail() { return $this->owner->email; } } %% Конструкция выглядит так - %%routeNotificationFor{CHANNELNAME}%%, и в данном случае вам надо вернуть почтовый адрес для отправки. Другие каналы передачи будут ожидать возвращения других данных от своих методов маршрутизации. == Как отправлять уведомления == Есть два пути отправки уведомлений. Первый - использование фасада %%Notification%%: %% Notification::send(User::first(), new WorkoutAssigned($workout)); %% Первый параметр указывает, кого надо уведомить. Вы можете передать либо конкретный экземпляр модели, либо передать всю коллекцию. Второй параметр - экземпляр вашего уведомления: %% Notification::send(User::all(), new DowntimePlanned($date)); %% Второй путь - использование метода %%notify()%% на вашей модели, которая импортирует типаж %%Notifiable%% (например, стандартный класс %%User%%): %% $user->notify(new WorkoutAssigned($workout)); %% .(alert) Перед отправкой первого уведомления задайте новое свойство "name" в файле %%(t)config/app.php%%. Это имя вашего приложения, которое будет отображаться в заголовке и в подвале ваших писем. Вот как выглядит почтовое уведомление по умолчанию для нашего класса Workout Assigned: {{Image /packages/proger/habravel/uploads/689-default-email-notification.png}} == Доступные каналы передачи == Какие ещё доступны каналы, кроме %%mail%%? Стандартные каналы: %%database%%, %%broadcast%%, %%nexmo%% и %%slack%%, но вы можете найти и другие на сайте ((http://laravel-notification-channels.com/ каналов уведомлений Laravel)), созданном сообществом. Выше я упомянул о программном определении того, какой канал использовать для уведомления пользователя. Вот один из способов, приведённый в документации: %% public function via($notifiable) { return $notifiable->prefers_sms ? ['nexmo'] : ['mail', 'database']; } %% Также вы можете встроить эту логику в саму модель: %% // в уведомлении public function via($notifiable) { return $notifiable->preferredNotificationChannel(); } // в классе User public function preferredNotificationChannel() { return PresenceChecker::isOnline($this) ? ['broadcast'] : ['mail']; } %% === Почтовый канал === Мы уже рассмотрели основы отправки уведомлений по почте, но есть ещё множество доступных настроек. Вы можете настроить тему письма (которая по умолчанию берётся из имени класса уведомления, например, для класса "WorkoutAssigned" тема будет "Workout Assigned") с помощью метода %%subject()%%: %% public function toMail($notifiable) { return (new MailMessage) ->subject('Вам назначена новая тренировка!') ... } %% Вы можете настроить приветствие (по умолчанию "Hello!") с помощью метода %%greeting()%%: %% public function toMail($notifiable) { return (new MailMessage) ->greeting("За делоооооооо!") ... } %% Используйте шаблон "error", чтобы изменить цвет с синего на красный: %% public function toMail($notifiable) { return (new MailMessage) ->error() ... } %% И наконец, вы можете опубликовать и настроить шаблон для писем: %%(sh) php artisan vendor:publish --tag=laravel-notifications %% Теперь текстовые и HTML шаблоны будут находиться в %%(t)resources/views/vendor/notifications%%. === Канал базы данных === Канал отправки уведомлений в базу данных сохраняет каждое уведомление в таблице БД, а вы можете обработать их в приложении, как пожелаете. Вы можете создать миграцию для этой таблицы командой %%(sh)php artisan notifications:table%%. Если вы не определите метод %%toDatabase()%% для вашего уведомления, то Laravel использует метод %%toArray()%% для определения данных, которые необходимо поместить в БД. Но вы можете настроить и это. Чтобы вы ни вернули из методов %%toDatabase()%% и %%toArray()%%, в столбце %%(t)data%% базы данных оно будет в формате JSON. %% // в уведомлении public function toDatabase($notifiable) { return [ 'trainee_id' => $notifiable->id, 'workout_id' => $this->workout->id ]; } %% Вы легко можете получить эти уведомления через отношение %%notifications()%%, которое добавляется в вашу модель типажом %%Notifiable%%. Благодаря этому появляются дополнительные возможности работы с "прочитанными" и "непрочитанными" уведомлениями. У каждого уведомления есть метод %%markAsRead()%%, который используется для изменения свойства %%(t)read_at%%. А для получения всех "непрочитанных" уведомлений используется метод %%unreadNotifications()%% на модели: %% foreach ($user->notifications as $notification) { // произвести действия $notification->markAsRead(); } // позже... foreach ($user->unreadNotifications as $notification) { // новые! } %% === Широковещательный канал (broadcast) === Если вы не знакомы с вещанием событий Laravel, то для понимания широковещательного канала вы можете ознакомиться с моей ((https://mattstauffer.co/blog/broadcasting-events-with-pusher-socket-in-laravel-5.1 статьёй)) о вещании событий Laravel. Широковещательный канал рассылает события с данными из ваших уведомлений в ваш Websocket-клиент. Для этих уведомлений он использует закрытый канал %%(t){notifiableClassNameDotNotated}.{id}%%, т.е. при уведомлении пользователя 15 вещание будет происходить в закрытый канал %%(t)App.User.15%%. Как и другие методы, вещание уведомлений по умолчанию берёт структуру данных из %%toArray()%%, если вы не определите метод %%toBroadcast()%%. Если вы используете Laravel Echo, то можете подписаться на широковещательный канал пользователя с помощью такого кода: %% var userId = 15; // задать в каком-либо месте Echo.private('App.User.' + userId) .notification((notification) => { console.log(notification); }); %% === Канал Nexmo === С помощью канала Nexmo можно легко посылать уведомления по SMS. Вам надо указать ваши учётные данные Nexmo в %%(t)config/services.php%% в ключе %%(t)nexmo%% вот так: %% 'nexmo' => [ 'key' => env('NEXMO_KEY'), 'secret' => env('NEXMO_SECRET'), 'sms_from' => '15558675309', ], %% И определить метод %%toNexmo()%%, который возвращает экземпляр %%Illuminate\Notifications\Message\NexmoMessage%%: %% public function toNexmo($notifiable) { return (new NexmoMessage) ->content('Смотри, это у тебя в телефоне!'); } %% Если почтовый канал проверяет свойство %%(t)email%% уведомляемого, то канал Nexmo проверяет свойство %%(t)phone_number%% для отправки сообщения. Это можно настроить методом %%routeNotificationForNexmo()%%: %% // в уведомлении public function routeNotificationForNexmo() { return $this->sms_number; } %% === Канал Slack === Канал Slack вещает ваши уведомления в Slack-чат. .(alert) Чтобы использовать уведомления Slack, вам необходимо установить Guzzle через Composer: %%(sh)composer require guzzlehttp/guzzle%%. Сначала зайдите в свой аккаунт Slack, раздел "Apps and Integrations" (Приложения и Интеграция %%(html)https://{yourteam}.slack.com/apps%%). Выберите тип "Incoming Webhook" (Входящий Webhook) и добавьте новую конфигурацию. Вы можете указать чат для отправки и другие настройки. {{Image /packages/proger/habravel/uploads/689-slack-integration-config-screen.png, height=300px}} Скопируйте Webhook URL и вернитесь к своему Laravel-приложению. Ваш уведомляемый должен иметь метод %%routeNotificationForSlack()%% , который должен возвращать этот Webhook URL: %% public function routeNotificationForSlack() { return $this->slack_webhook_url; } %% Теперь давайте настроим уведомление. Вы можете прочитать подробнее в ((https://laravel.com/docs/5.3/notifications#slack-notifications документации)), а здесь я приведу небольшой пример того, что можно сделать в методе %%toSlack()%%: %% public function toSlack($notifiable) { $url = url('/invoices/' . $this->invoice->id); return (new SlackMessage) ->success() ->content('Один из ваших счетов оплачен!') ->attachment(function ($attachment) use ($url) { $attachment->title('Счёт 1322', $url) ->fields([ 'Title' => 'Расходы на сервер', 'Amount' => '$1,234', 'Via' => 'American Express', 'Was Overdue' => ':-1:', ]); }); } %% {{Image /packages/proger/habravel/uploads/689-complex-slack-notification-example.png}} Также вы можете оставить его максимально простым, просто сгенерируйте %%SlackMessage%% и определите только его свойство %%content%%: %% public function toSlack($notifiable) { return (new SlackMessage) ->content('Один из ваших счетов оплачен!'); } %% === Очередь для уведомлений === Все уведомления, реализующие интерфейс %%ShouldQueue%% и импортирующие типаж %%Queueable%%, будут помещаться в вашу очередь и отправляться асинхронно. Поскольку большинство каналов требуют отправки HTTP-запросов, то крайне рекомендуется использовать очередь для уведомлений. == Заключение == Вот и всё! Это великолепно. Уведомления так просты и надёжны, что вам не придётся больше искать какие-то другие инструменты для этого (почту, Slack SDK напрямую и т.п.), особенно когда вы увидите, сколько каналов создано сообществом. Сумасшедшие люди. Но как и всегда, с большими возможностями приходит большая ответственность. Будьте аккуратны в обращении с пользователями, не переборщите с уведомлениями. Ну что ж, в бой. Уведомляйте!