Laravelでイベントを操作します。 記事を公開するときにプッシュ通知を送信する

私のブログの最初の記事の1つに対するコメントで、読者はOnesignalサービスを介してプッシュ通知を固定するようにアドバイスされました。 もちろん、通知自体、サービスについては知っていました-いいえ。

グーグルで検索するのは簡単でしたが、これは、すべてのプラットフォームとデバイスにわたって、まったく異なる種類のプッシュ通知を送信できるサービスであることがわかりました。 同時に、便利なコントロール/レポートパネル、遅延送信の可能性などがあります。

サービス自体のセットアップについては説明しません。 ロシアには対応するものがあり、必要に応じてリンクを簡単に見つけることができます。 そして、スピーチはもはやサービスそのものではなく、Laravelの正しいアプリケーションアーキテクチャについてです。



統合



サービスの操作は、ユーザーのサブスクライブと通知の送信という2つの部分に分かれています。 したがって、統合は2つの部分で構成されます。

1)クライアント側:ホストjavascript

2)サーバー側:私たちは怠け者なので、Onesignal管理エリアにアクセスして、手動で配布するたびにメッセージを投稿するのは、私たちの方法ではありません。 スマートカーを任せるべきです! そして、lo! Onesignalには、このためのJSON APIがあります。

クライアント部



サービスWebサイトにすべてが説明されているため、詳細についても説明しません。 私は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>
      
      





これで、クライアント部分の構成が完了しました。

サーバー部分。 建築



楽しい部分に到達します。

タスク:投稿時(記事)投稿プッシュ通知。

ただし、同時に、記事を公開するとすぐに、複数のアクションを実行するには100%が必要になることに留意してください。 たとえば、テキストをYandex Webmasterの「Original Texts」に送信したり、Twitterでツイートしたりします。

したがって、何らかの方法でプロセス全体を何らかの形で整理し、すべてをモデルに押し込んだり、コントローラーに感謝したりする必要はありません。



推測しましょう。 記事自体の公開は何ですか? そうです- イベント ! それでは、 eventsを使用しましょう。 胸部での実装は非常に優れています。

まあ、もちろん、イベントについてはタイトルにネタバレがあったので、誰もがすぐに推測した)


ドキュメントによると、イベントを登録してクラス自体を作成する方法はいくつかあります。 最も便利なオプションについて説明しましょう。

コードを書く



これを行います:app / Providers / EventServiceProvider.phpで、イベントとそのリスナーを示します。 イベントはPostPublishedEventと呼ばれ、リスナーはPostActionsListenerです。

 protected $listen = [ 'App\Events\PostPublishedEvent' => [ 'App\Listeners\PostActionsListener', ], ];
      
      





次に、コンソールに移動してコマンドを実行します

 php artisan event:generate
      
      





チームは、イベントクラスアプリ/イベント/ PostPublishedEvent.phpとそのリスナーアプリ/リスナー/ PostActionsListener.phpを作成します

最初にイベントクラスを編集し、ブログ投稿のインスタンスを渡します。

 public $post; /** * PostPublishedEvent constructor. * @param Post $post */ public function __construct(Post $post) { $this->post = $post; }
      
      





以下のコードでは、クラスを接続することを忘れません。

 use App\Models\Post;
      
      





リスナーアプリ/リスナー/ PostActionsListener.phpに移動します

理由で彼にそのように電話しました!

イベントの種類ごとにリスナーを作成しないようにするため(多くはないだろうと思う)、イベントを開始することにしました。

イベントのクラスのインスタンスに基づいて、正確に何が行われるかを整理します。

このようなもの

 /** * Handle the event. * * @param Event $event * @return void */ public function handle(Event $event) { if ($event instanceof PostPublishedEvent) { //   } }
      
      





これで、PostPublishedEventイベントが何らかの形で発生します。 モデルを保存しながらこれを行うことをお勧めします。

この場合、記事には2つのステータス(ステータスフィールド) Draft / Publishedがあります。

私は通常、クラス定数でステータスを実行します。 この場合、次のようになります。

 const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1;
      
      





ステータスを「公開済み」に変更し、通知を送信する必要がある場合。

このプロセスが確実に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



を実行します

イベントコール



これで、すべてがイベント自体を引き起こす準備ができました。

Laravelでモデルを保存するプロセスをキャッチするために、特別に訓練された(再び) イベントがあります

Postモデルで静的なブートメソッドを作成し、saveイベントのコメント、コメントの説明にリスナーを追加します。

 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モデル用のファクトリーを作成しましょう

ファイルデータベース/ファクトリー/ PostFactory.php:

 $factory->define(App\Models\Post::class, function (Faker\Generator $faker) { return [ 'title' => $faker->text(100), 'publish_date' => date('Ymd 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 ]; });
      
      





そして、テスト/ PostCreateTest.phpは、これまでのところ1つのメソッドでテストします。

 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)





イベント処理、プッシュ通知の送信



イベントは処理せず、onesignal.comサービスを介してプッシュ通知を送信するだけです。

サービスWebサイトにアクセスし、 REST APIを使用してマニュアルを吸います

私たちはメッセージを送る手順に興味があります

すべてのパラメーターについて詳しく説明します。サンプルコードがあります。



curl_ *関数を使用する代わりに、使い慣れたanlutro / curlラッパーパッケージをインストールします。

composer require anlutro/curl



コンソールでcomposer require anlutro/curl





送信手順全体を個別のハンドラーアプリ/ 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)送信のステータスを記録するとき、サービスの応答を考慮せず、サービスが送信を拒否した場合、処理された記事を考慮し、通知の再送信を試みません。

したがって、 このソリューションを運用サーバーで使用することはお勧めしません。

今後のレッスンでこれらの欠点を修正します。 継続を待ちます(最初のコメントのネタバレ:)

UPD



続きの記事Laravelでのイベントの操作。 非同期キュー処理。




All Articles