В комментариях к одной из первых статей в моем блоге читатель посоветовал мне прикрутить push-уведомления через сервис "Onesignal" На тот момент я понятия не имел, что это за зверь и с чем его едят. Про сами уведомления я, конечно, знал, про сервис — нет.
Легко нагуглил и оказалось, что это сервис, который позволяет рассылать push уведомления абсолютно разного рода, по всем платформам и девайсам. При этом имеет удобную панель управления/отчетности, возможность отложенной отправки и тд.
На настройке самого сервиса останавливаться не буду. Есть и его российские аналоги, ссылки при необходимости легко находятся. Да и речь больше не о самом сервисе, а о правильной архитектуре приложения на Laravel.
Работа с сервисом делится на 2 части: подписка пользователей и рассылка уведомлений. Поэтому и интеграция состоит из двух частей:
1) Клиентская часть: размещаем javascript
2) Серверная часть: мы люди ленивые, поэтому ходить в админку Onesignal и постить каждый раз сообщения для рассылки вручную – не наш метод. Нам бы это дело доверить умным машинам! И, о чудо! Для этого у onesignal есть JSON API.
Тоже подробно расписывать не стану, тк все описано на сайте сервиса. Скажу лишь, что есть 2 пути. Простой: тупо разместить их Javascript, который генерит кнопку для подписки. И более долгий: верстать кнопку ручками, по клику вызывать их URL.
Как вы уже догадались, я выбрал простой путь )
Ниже приведу код для размещения на странице, т.к. я не нашел метода для простой локализации всего этого около-кнопочного интерфейса, я переопределил все JS сообщения, благо их библиотека это позволяет. Если кому-то нужна русская локализация, можно взять мой, уже переведенный код.
На этом настройка клиентской части завершена.
Приступаем к самому интересному.
Задача: при размещении поста (статьи) разослать push уведомления.
Но, при этом держим в уме, что скоро при публикации статьи нам 100% понадобится выполнить еще не одно действие. Например, послать текст в «Оригинальные тексты» яндекс-вебмастера, чирикнуть в твиттер и тп.
Поэтому надо весь этот процесс как-то фэншуйненько организовать, а не пихать все в модель или, упасибох, контроллер.
Давайте порассуждаем. Сама публикация статьи — это что? Правильно – событие! Так давайте же и использовать события. Их реализация в ларе очень хороша.
Согласно документации есть несколько способов регистрации событий и создания самих классов. Остановимся на самом удобном варианте.
Мы поступим так: в app/Providers/EventServiceProvider.php укажем наше событие и его слушателя. Событие назовем PostPublishedEvent, слушателя — PostActionsListener.
Затем идем в консоль и запускаем команду
Команда создаст классы события app/Events/PostPublishedEvent.php и его слушателя app/Listeners/PostActionsListener.php
Отредактируем сначала класс события, в него мы будем передавать экземпляр нашего блог-поста.
Здесь и далее по коду не забываем подключить классы.
Теперь переходим к слушателю app/Listeners/PostActionsListener.php
Я его обозвал таким образом не просто так!
Чтобы не плодить слушателей на каждый тип события (думаю их не много будут) я решил завести один.
Разруливать что именно выполнить будем исходя из того, экземпляр какого класса события пришел.
Примерно так
Теперь осталось каким-то образом сделать так, чтобы наше событие PostPublishedEvent произошло. Предлагаю пока это сделать при сохранении модели.
В нашем случае статья может иметь 2 статуса (поле status) Черновик / Опубликован.
Статусы я обычно делаю константами класса. В данном случае они выглядят так:
При смене статуса на «Опубликован» и надо разослать уведомления.
Для того чтобы удостовериться, что процесс этот произойдет один раз, заведем дополнительную колонку, флаг того, что уведомление по данному посту были разосланы.
Добавим дополнительное поле notify_status, его значения могут такими же что и у status.
Выполним в консоли:
Созданную миграцию отредактируем таким образом:
Выполним в консоли
Теперь все готово к тому, чтобы вызывать само событие.
Чтобы поймать процесс сохранения модели в Ларавел есть специально обученные (опять же) события.
Заведем в модели Post статичный метод boot И добавим в него слушателя на событие сохранения, объяснения в комментариях:
Самое время написать первый тест!
Нам необходимо протестировать: во-первых, что нужное событие при нужных условиях происходит, и во-вторых, что событие не происходит, когда не надо (статус = черновик например)
Если вы читали статью Первое приложение на Laravel. Пошаговое руководство (Часть 1),
вы уже знаете про фабрики моделей, и как они полезны для тестирования. Создадим свою фабрику для модели Post
файл database/factories/PostFactory.php:
И сам тест tests/PostCreateTest.php c одним пока методом:
Запустим phpunit. Должно быть все отлично
Теперь добавим обратную проверку того, что событие не возникает, на черновиках например:
Прогоняем phpunit:
Остались пустяки, всего лишь обработать событие и отправить пуш уведомления через сервис onesignal.com.
Идем на сайт сервиса и курим мануал по REST API.
Нас интересует процедура отправки сообщения.
Все параметры подробно описаны, пример кода есть.
Я вместо использования curl_* функций установлю знакомый мне пакет-обертку anlutro/curl.
В консоли
Все процедуру отправки оформим как отдельный хендлер app/Handlers/OneSignalHandler.php: Вот его код полностью. В комментариях опишу что к чему
Для хранения настроек onesignal я создал конфиг
config/onesignal.php
Сами настройки в .env
В конфиге фигурирует 'own_player_id’
Это мой ID подписчика из админки. Нужен он для тестов, чтобы отправлять уведомление только себе.
Отправка готова – самое время его протестировать. Сделать это очень просто, тк мы задали верную архитектуру и процесс отправки статьи по сути является изолированным.
Добавим в наш тест такой метод:
В консоли
Если тест не проходит, смотрим лог и исправляем то, что не нравится сервису
Осталось только добавить вызов в слушателя
На этом пока все, но наш код имеет ряд недостатков:
1) отправка у нас происходит в реальном времени при сохранении модели, если добавятся более тяжелые и медленные операции, до сохранения не дойдет и все упадет.
2) при записи статуса отправки мы не учитываем ответ сервиса, если сервис откажет в отправке, мы статью посчитаем обработанной и больше по ней пытаться отправить уведомления не будем.
Поэтому я не рекомендую использовать это решение на продакшен-сервер.
Будем эти недостатки исправлять в будущих уроках. Дождитесь продолжения (спойлер в первом комментарии :)
Легко нагуглил и оказалось, что это сервис, который позволяет рассылать push уведомления абсолютно разного рода, по всем платформам и девайсам. При этом имеет удобную панель управления/отчетности, возможность отложенной отправки и тд.
На настройке самого сервиса останавливаться не буду. Есть и его российские аналоги, ссылки при необходимости легко находятся. Да и речь больше не о самом сервисе, а о правильной архитектуре приложения на Laravel.
Интеграция
Работа с сервисом делится на 2 части: подписка пользователей и рассылка уведомлений. Поэтому и интеграция состоит из двух частей:
1) Клиентская часть: размещаем javascript
2) Серверная часть: мы люди ленивые, поэтому ходить в админку Onesignal и постить каждый раз сообщения для рассылки вручную – не наш метод. Нам бы это дело доверить умным машинам! И, о чудо! Для этого у onesignal есть JSON API.
Клиентская часть
Тоже подробно расписывать не стану, тк все описано на сайте сервиса. Скажу лишь, что есть 2 пути. Простой: тупо разместить их Javascript, который генерит кнопку для подписки. И более долгий: верстать кнопку ручками, по клику вызывать их URL.
Как вы уже догадались, я выбрал простой путь )
Ниже приведу код для размещения на странице, т.к. я не нашел метода для простой локализации всего этого около-кнопочного интерфейса, я переопределил все JS сообщения, благо их библиотека это позволяет. Если кому-то нужна русская локализация, можно взять мой, уже переведенный код.
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async></script>
<script>
var OneSignal = OneSignal || [];
OneSignal.push(["init", {
appId: "мой id приложения",
subdomainName: 'laravel-news', //мой поддомен на onesignal.com (задается при настройке приложения)
notifyButton: {
enable: true, // Set to false to hide,
size: 'large', // One of 'small', 'medium', or 'large'
theme: 'default', // One of 'default' (red-white) or 'inverse" (whi-te-red)
position: 'bottom-right', // Either 'bottom-left' or 'bottom-right' offset: {
offset: {
bottom: '90px',
left: '0px', // Only applied if bottom-left
right: '80px' // Only applied if bottom-right
},
text: {
"tip.state.unsubscribed": "Получать уведомления о новых статьях прямо в браузере",
"tip.state.subscribed": "Вы подписаны на уведомления",
"tip.state.blocked": "Вы заблокировали уведомления",
"message.prenotify": "Не забудьте подписаться на уведомления о новых статьях",
"message.action.subscribed": "Спасибо за подписку!",
"message.action.resubscribed": "Вы подписаны на уведомления",
"message.action.unsubscribed": "Увы, теперь вы не сможете получать уведомления о самых интересных статьях",
"dialog.main.title": "Настройки уведомлений",
"dialog.main.button.subscribe": "Подписаться",
"dialog.main.button.unsubscribe": "Поступить опрометчиво и отписаться",
"dialog.blocked.title": "Снова получать уведомления о самых интересных статьях",
"dialog.blocked.message": "Следуйте этим инструкциям, чтобы разрешить уведомления:"
}
},
prenotify: true, // Show an icon with 1 unread message for first-time site visitors
showCredit: false, // Hide the OneSignal logo
welcomeNotification: {
"title": "Новости Laravel",
"message": "Спасибо за подписку!"
},
promptOptions: {
showCredit: false, // Hide Powered by OneSignal
actionMessage: "просит разрешения получать уведомления:",
exampleNotificationTitleDesktop: "Это просто тестовое сообщение",
exampleNotificationMessageDesktop: "Уведомления будут приходить на Ваш ПК",
exampleNotificationTitleMobile: " Пример уведомления",
exampleNotificationMessageMobile: "Уведомления будут приходить на Ваше устройстве",
exampleNotificationCaption: "(можно отписаться в любое время)",
acceptButtonText: "Продолжить".toUpperCase(),
cancelButtonText: "Нет, спасибо".toUpperCase()
}
}]);
</script>
На этом настройка клиентской части завершена.
Серверная часть. Архитектура.
Приступаем к самому интересному.
Задача: при размещении поста (статьи) разослать push уведомления.
Но, при этом держим в уме, что скоро при публикации статьи нам 100% понадобится выполнить еще не одно действие. Например, послать текст в «Оригинальные тексты» яндекс-вебмастера, чирикнуть в твиттер и тп.
Поэтому надо весь этот процесс как-то фэншуйненько организовать, а не пихать все в модель или, упасибох, контроллер.
Давайте порассуждаем. Сама публикация статьи — это что? Правильно – событие! Так давайте же и использовать события. Их реализация в ларе очень хороша.
Ну конечно, про события был спойлер в заголовке, поэтому все сразу догадались )
Согласно документации есть несколько способов регистрации событий и создания самих классов. Остановимся на самом удобном варианте.
Пишем код
Мы поступим так: в app/Providers/EventServiceProvider.php укажем наше событие и его слушателя. Событие назовем PostPublishedEvent, слушателя — PostActionsListener.
protected $listen = [
'App\Events\PostPublishedEvent' => [
'App\Listeners\PostActionsListener',
],
];
Затем идем в консоль и запускаем команду
php artisan event:generate
Команда создаст классы события app/Events/PostPublishedEvent.php и его слушателя app/Listeners/PostActionsListener.php
Отредактируем сначала класс события, в него мы будем передавать экземпляр нашего блог-поста.
public $post;
/**
* PostPublishedEvent constructor.
* @param Post $post
*/
public function __construct(Post $post)
{
$this->post = $post;
}
Здесь и далее по коду не забываем подключить классы.
use App\Models\Post;
Теперь переходим к слушателю app/Listeners/PostActionsListener.php
Я его обозвал таким образом не просто так!
Чтобы не плодить слушателей на каждый тип события (думаю их не много будут) я решил завести один.
Разруливать что именно выполнить будем исходя из того, экземпляр какого класса события пришел.
Примерно так
/**
* Handle the event.
*
* @param Event $event
* @return void
*/
public function handle(Event $event)
{
if ($event instanceof PostPublishedEvent)
{
//тут будет магия
}
}
Теперь осталось каким-то образом сделать так, чтобы наше событие PostPublishedEvent произошло. Предлагаю пока это сделать при сохранении модели.
В нашем случае статья может иметь 2 статуса (поле status) Черновик / Опубликован.
Статусы я обычно делаю константами класса. В данном случае они выглядят так:
const STATUS_DRAFT = 0;
const STATUS_PUBLISHED = 1;
При смене статуса на «Опубликован» и надо разослать уведомления.
Для того чтобы удостовериться, что процесс этот произойдет один раз, заведем дополнительную колонку, флаг того, что уведомление по данному посту были разосланы.
Добавим дополнительное поле notify_status, его значения могут такими же что и у status.
Выполним в консоли:
php artisan make:migration add_noty_status_to_post_table --table=post
Созданную миграцию отредактируем таким образом:
public function up()
{
Schema::table('post', function (Blueprint $table) {
$table->tinyInteger('notify_status')->default(0);
});
}
Выполним в консоли
php artisan migrate
Вызов события
Теперь все готово к тому, чтобы вызывать само событие.
Чтобы поймать процесс сохранения модели в Ларавел есть специально обученные (опять же) события.
Заведем в модели Post статичный метод boot И добавим в него слушателя на событие сохранения, объяснения в комментариях:
public static function boot()
{
static::saving(function($instance)
{
//Мы проверяем статус статьи – если он «Опубликован», смотрим на статус оповещения, если он еще не «Опубликован»
if ($instance->status == self::STATUS_PUBLISHED
&& $instance->notify_status < self::STATUS_PUBLISHED){
//то устанавливаемый статус оповещения в «опубликован»
$instance->notify_status = self::STATUS_PUBLISHED;
//и «выстреливаем» событие PostPublishedEvent, передавая в него собственный инстанс.
\Event::fire(new PostPublishedEvent($instance));
});
parent::boot();
}
Тесты
Самое время написать первый тест!
Нам необходимо протестировать: во-первых, что нужное событие при нужных условиях происходит, и во-вторых, что событие не происходит, когда не надо (статус = черновик например)
Если вы читали статью Первое приложение на Laravel. Пошаговое руководство (Часть 1),
вы уже знаете про фабрики моделей, и как они полезны для тестирования. Создадим свою фабрику для модели Post
файл database/factories/PostFactory.php:
$factory->define(App\Models\Post::class, function (Faker\Generator $faker) {
return [
'title' => $faker->text(100),
'publish_date' => date('Y-m-d H:i'),
'short_text' => $faker->text(300),
'full_text' => $faker->realText(1000),
'slug' => str_random(50),
'status' => \App\Models\Post::STATUS_PUBLISHED,
'category_id' => 1
];
});
И сам тест tests/PostCreateTest.php c одним пока методом:
class PostCreateTest extends TestCase
{
public function testPublishEvent()
{
//говорим, что ожидаем событие \App\Events\PostPublishedEvent
$this -> expectsEvents(\App\Events\PostPublishedEvent::class);
//Создаем экземпляр поста с записью в бд
$post = factory(App\Models\Post::class)->create();
//и проверяем на месте ли он
$this -> seeInDatabase('post', ['title' => $post->title]);
//затем удаляем
$post -> delete();
}
}
Обратите внимани: при тестировании событий, сами события не возникают. Регистрируется только факт их возникновения или не возникновения
Запустим phpunit. Должно быть все отлично
OK (1 test, 1 assertion)
Теперь добавим обратную проверку того, что событие не возникает, на черновиках например:
public function testNoPublishEvent()
{
$this->doesntExpectEvents(\App\Events\PostPublishedEvent::class);
// При создании экземпляра статьи – переопределяем status.
$post = factory(App\Models\Post::class)->create(
[
'status' => App\Models\Post::STATUS_DRAFT
]);
$this->seeInDatabase('post', ['title' => $post->title]);
$post->delete();
}
Прогоняем phpunit:
OK (2 tests, 2 assertions)
Обработка события, отправка push уведомлений
Остались пустяки, всего лишь обработать событие и отправить пуш уведомления через сервис onesignal.com.
Идем на сайт сервиса и курим мануал по REST API.
Нас интересует процедура отправки сообщения.
Все параметры подробно описаны, пример кода есть.
Я вместо использования curl_* функций установлю знакомый мне пакет-обертку anlutro/curl.
В консоли
composer require anlutro/curl
Все процедуру отправки оформим как отдельный хендлер app/Handlers/OneSignalHandler.php: Вот его код полностью. В комментариях опишу что к чему
<?php namespace App\Handlers;
use anlutro\cURL\cURL;
use App\Models\Post;
class OneSignalHandler
{
//признак тестовой отправки
private $test = false;
// по умолчанию отправляем "боевое сообщение"
public function __construct($test=false)
{
$this->test = $test;
}
//Метод sendNotify принимает на вход инстанс статьи.
public function sendNotify(Post $post)
{
//Про конфиг ниже
$config = \Config::get('onesignal');
//если app_id вообще задан, то отправляем
if (!empty($config['app_id'])) {
//Cоставляет параметры согласно мануалу
$data = array(
'app_id' => $config['app_id'],
'contents' =>
[
"en" => $post->short_text
],
'headings' =>
[
"en" => $post->title
],
//(я использую только WebPush уведомления)
'isAnyWeb' => true,
'chrome_web_icon' => $config['icon_url'],
'firefox_icon' => $config['icon_url'],
'url' => $post->link
);
//Если параметр test == true То мы в получателя добавляем только себя,
if ($this->test)
{
$data['include_player_ids'] = [$config['own_player_id']];
} else {
//если нет - то всех.
$data['included_segments'] = ["All"];
}
//Дата отложенной отправки! Очень круто!
if (strtotime($post->publish_date) > time()) {
$data['send_after'] = date(DATE_RFC2822, strtotime($post->publish_date));
$data['delayed_option'] = 'timezone';
$data['delivery_time_of_day'] = '10:00AM';
}
$curl = new cURL();
$req = $curl->newJsonRequest('post',$config['url'], $data)->setHeader('Authorization', 'Basic '.$config['api_key']);
$result = $req->send();
//В случае неудачи, пишем ответ в лог.
if ($result->statusCode <> 200) {
\Log::error('Unable to push to Onesignal', ['error' => $result->body]);
return false;
}
$result = json_decode($result->body);
if ($result->id)
{
//Если запрос удачен - возвращаем кол-во получателей.
return $result->recipients;
}
}
}
}
Настройки
Для хранения настроек onesignal я создал конфиг
config/onesignal.php
<?php
return [
'app_id' => env('ONESIGNAL_APP_ID',''),
'api_key' => env('ONESIGNAL_API_KEY',''),
'url' => env('ONESIGNAL_URL','https://onesignal.com/api/v1/notifications'),
'icon_url' => env('ONESIGNAL_ICON_URL',''),
'own_player_id' => env('ONESIGNAL_OWN_PLAYER_ID','')
];
Сами настройки в .env
ONESIGNAL_APP_ID = 256aa8d2….
ONESIGNAL_API_KEY = YWR…..
ONESIGNAL_ICON_URL = http://laravel-news.ru/images/laravel_logo_80.jpg
ONESIGNAL_URL = https://onesignal.com/api/v1/notifications
ONESIGNAL_OWN_PLAYER_ID = 830…
В конфиге фигурирует 'own_player_id’
Это мой ID подписчика из админки. Нужен он для тестов, чтобы отправлять уведомление только себе.
Тестирование
Отправка готова – самое время его протестировать. Сделать это очень просто, тк мы задали верную архитектуру и процесс отправки статьи по сути является изолированным.
Добавим в наш тест такой метод:
public function testSendOnesignal()
{
//В нем мы создаем экземпляр статьи (без записи с бд)
$post = factory(App\Models\Post::class)->make();
//Инициализируем наш обработчик с параметром test = true
$handler = new \App\Handlers\OneSignalHandler(true);
//и делаем отправку
$result = $handler->sendNotify($post);
//Должны получить 1, тк отправляем уведомление только себе.
$this->assertEquals(1,$result);
}
В консоли
phpunit
– тест успешно проходит и выскакивает уведомление (иногда бывают задержки до нескольких минут)Если тест не проходит, смотрим лог и исправляем то, что не нравится сервису
Финальный аккорд
Осталось только добавить вызов в слушателя
/**
* Handle the event.
*
* @param Event $event
* @return void
*/
public function handle(Event $event)
{
if ($event instanceof PostPublishedEvent)
{
(new OneSignalHandler())->sendNotify($event->post);
}
}
Обратите внимание
На этом пока все, но наш код имеет ряд недостатков:
1) отправка у нас происходит в реальном времени при сохранении модели, если добавятся более тяжелые и медленные операции, до сохранения не дойдет и все упадет.
2) при записи статуса отправки мы не учитываем ответ сервиса, если сервис откажет в отправке, мы статью посчитаем обработанной и больше по ней пытаться отправить уведомления не будем.
Поэтому я не рекомендую использовать это решение на продакшен-сервер.
Будем эти недостатки исправлять в будущих уроках. Дождитесь продолжения (спойлер в первом комментарии :)
difiso
Недостатки решаются обработкой событий в асинхронном режиме.
Для этого надо чтобы
Listener
реализовывал пустой интерфейсShouldQueue
. В этом случае все события обрабатываются очередью в фоне (ну да, надо запуститьphp artisan queue:listen
илиphp artisan queue:work
, в доках по очередям это есть).Метод
Post::saving
выполняется ДО сохранения модели в базу. В этом методе вы не можете быть уверены в том, что модель сохранится в базу. Отправлять уведомления надо только в том случае, когда пост сохранился, т.е. использовать методPost::saved
И, соответственно, очень опрометчиво менять
$notify_status
до отправки уведомлений. В чем проблема менять, когда точно известно, что уведомление ушло — после получения ответа от сервера?Rencom
Если Вы не заметили, я все это указал конце, очереди буду рассматривать отдельно. Вместе с расширением функционала (не только push уведомления планируются)
Уровень статьи — для тех, кто делает первые (или вторые) шаги. Много информации сразу сбивает с толку.
difiso
Про очереди не заметил, виноват. Но логику-то зачем воротить? Сначала пометить сделанным, а потом надеяться что внешний сервер ответит успехом?
Rencom
Чтобы был лишний повод для рефакторинга. Показать, как делают обычно :)) и как это исправить
Sway
Вот не стоит так делать в этом случае. Одно дело — когда в одной статье рассматривается и проблема и ее решение, а совсем другое, когда в 1й статье умышленно делается ошибки/недочет, которые исправляются в другой статье. К моменту выхода 2й статьи уже будет поздно что-либо исправлять, а многие ее и не прочитают, особенно если найдут такие ляпы.
Rencom
В статье не просто молча допущен недочет (не смертельный кстати).
В ней отдельным параграфом это указано. Лишний повод продумать решение самому, как это сделал difiso выше
Sway
На мой взгляд от этого легче не становится. Ошибки есть, они не исправлены. Заставлять читателя думать там, где это необязательно, не всегда уместно. Я вот зашел почитать как другой человек работает с событиями и для чего их использует.
Кстати, не вижу особого смысла использовать для этой задачи события. Этим вы только усложняете понимание происходящего т.к. вызов события никак не передает суть того, что оно будет делать. К тому же это событие всегда вызывается из одного и того же места и имеет один обработчик (даже если их будет и несколько, то все равно смысла мало).
Что мешает сделать вот так?
И при необходимости добавлять сюда действия другого типа одной строчкой.
Ведь подумайте — onBeforeSave() — это по сути обработчик события (префикс "on" на это толсто намекает). А вы пихаете вызов другого события внутрь обработчика события. Жуть просто.
А теперь давайте рассмотрим почему мой вариант лучше на примере того как человек читает код.
Ваш вариант глазами другого человека, который знает Laravel:
2.1. Проверка статусов поста и нотификации (А предполагается ли такая ситуация что пост был изменен после публикации и рассылки?)
2.2. Устанавливаем новый статус нотификации (стоп! какого? мы ж ничего не сделали!)
2.3. Отправляем событие PostPublishedEvent
3.1. Открываем класс события (ну как обычно — ctrl+click, привыкли уже). Хм… В нем нет никакой инфы о его деятельности.
3.2. Ааа! Точно! У него ж должен быть listener. Таак и где среди этой сотни обработчиков нужный нам (я это к тому что с таким подходом в более-менее большом проекте обработчиков будет навалом)?
3.3. Нашли! Ура! Итак, наконец-то я узнаю что он делает! (new OneSignalHandler())->sendNotify($event->post);
3.4. Что????? И это все???? И я перелопатил хренову тучу классов/файлов ради этого????
Я утрирую, дальше ведь там будут еще другие действия, но все-равно путь длинноват.
Мой вариант:
2.1. Проверка статусов поста и нотификации (А предполагается ли такая ситуация что пост был изменен после публикации и рассылки?)
2.2. Устанавливаем новый статус нотификации (стоп! какого? мы ж ничего не сделали!)
2.3. Отправляем нотификацию через OneSignalHandler (опа! да тут же косяк! статус меняется независимо от того отправилась нотификация или, да еще и отправка происходит до сохранения данных, что вообще-то совершенно неправильно — вдруг не сохраниться?)
Надеюсь, вы понимаете что излишнее усложнение ради мнимого удобства приводят к тому что вы не найдете очевидных багов в самых простых местах пока эти баги не вылезут во всей красе. А если проект будет поддерживать другой человек?
События — хорошая штука, но применять их нужно не бездумно везде где захочется — будет только хуже.
Я пока нашел всего 1 применение для очень специфического случая, где без них был бы полнейший кошмар. Больше нигде не применял — не нашлось места. Знаю только что они применяются в коде Laravel, и там это обосновано — не нужно делать 100500 методов onSomeAction(). События удобны для библиотек, когда неизвестно какие действия захочет выполнить использующий либу программист.
Еще:
Rencom
Спасибо за такой развернутый комментарий ) Хочу напомнить, что это обучающий материал, цель его показать как можно работать с событиями, удобно это или нет, каждый решит сам.
Метод OnBeforeSave тут правда лишний, мой косяк, тк в оригинале я наследуюсь от своего CrudModel, в котором находится boot(), получается что дернул из разных мест: получил в итоге масло масляное
В следующей статье расскажу про очереди. Я это изначально планировал.
Rencom
Добавил в текст жирное предостережение.