Laravelイベントでリアルタイムのシャウトボックスを作成する
序章
Laravelは間違いなく非常に強力なフレームワークであり、多くのバッテリーが含まれています。 私がLaravelを気に入っている理由の1つは、Laravelがイベントに基づいて構築されているという事実です。
Laravelイベントとは何ですか?
イベントは、プログラムによって処理される可能性のあるプログラムによって認識されるアクションまたは発生です。 Laravelでのイベントの例は次のとおりです。
- 新しいユーザーがサインアップしました
- コメントが投稿されました
- ユーザーがブログ投稿を作成する
- ユーザーは写真が好きです
- もっと…
なぜイベントを使用するのですか?
イベントを使用する理由は、イベントによってアプリケーションの懸念事項を分離し、アプリケーションのアクションにフックするためのメカニズムを作成できるためです。 イベントが発生した場合、イベントはその実装について何も知る必要はありません。イベントが知る必要があるのは、アクションが実行され、イベントがトリガーされ、イベントがリスナーまたはサブスクライバーにデータを送信することだけです。それに対処します。
イベントがなければ、1か所にたくさんのロジックがあります
イベントなしでアプリがどのように見えるかを示すために、次のようなことを行います。
Route::get('signup', function() { // create the new user $user = User::create($userData); // a user is created, do things! // send the user a confirmation email // set up that users account // email them introductory information // do more stuff });
リストが少し長くなり始める方法がわかります。 これをイベントに分離して、多くのロジックをイベント自体から分離することができます。
イベントを使用するもう1つの理由は、アプリケーションのデータ処理を高速化するためです。
注: Pub Sub に既に精通している場合、イベントは新しい概念ではありません。
イベントを使用するタイミング
引き続きソーシャルブログアプリケーションを使用している場合、ユーザーがブログ投稿を作成すると、次のようなチェックリストが作成される可能性があります。
- REST APIを使用して、盗用の投稿を確認します。
- 新しい投稿をフォロワーに通知します。
- ソーシャルネットワークや検索エンジンなどに投稿を送信します。
このようなライニングアクションはアプリケーションの速度を低下させる可能性があり、イベントを使用してこのボトルネックを取り除くことができます。
Laravelは、次のような多くのシステムイベントもトリガーします。
- Laravelは、EloquentがCRUD操作を実行するたびにイベントをトリガーします。
- Laravelがブレードテンプレートをコンパイルすると、イベントが発生します。
これらは、Laravelによって発生したイベントのほんの一部です。
イベントの定義
Laravelでイベントを生成するには、古き良きartisanを使用するだけです。
php artisan make:event ActionDone
これにより、app/Events
ディレクトリにActionDone
という名前のクラスが作成され、次のようになります。
<?php namespace App\Events; use App\Events\Event; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class ActionDone extends Event { use SerializesModels; /** * Create a new event instance. * * @return void */ public function __construct() { // } /** * Get the channels the event should be broadcast on. * * @return array */ public function broadcastOn() { return []; } }
イベントの作成は非常に簡単で、セットアップはほとんど必要ありません。
イベントリスナー(イベントが発生したとき)
イベントがトリガーされた後、アプリケーションはイベントの処理方法を知る必要があり、そのためにはリスナーが必要です。 Laravelでリスナーを生成するには、職人を使用するだけです。
php artisan make:listener ThingToDoAfterEventWasFired --event="ActionDone"
イベントのリスナーを生成するコマンドは、リッスンするイベントの名前を持つ必須の--event
フラグを受け取ります。 上記のコマンドは、app/Listeners
にThingToDoAfterEventWasFired
という名前のクラスを作成します。
<?php namespace App\Listeners; use App\Events\ActionDone; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class ThingToDoAfterEventWasFired { /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param ActionDone $event * @return void */ public function handle(ActionDone $event) { // } }
イベントリスナークラスでは、handleメソッドはLaravelによってプログラムでトリガーされ、トリガーされたイベントのクラスをタイプヒントします。
イベントの登録
あちこちでイベントを発生させる前に、Laravelのコマンドバスはイベントとそのリスナー、およびそれらの処理方法について知る必要があります。
イベントを登録するには、app/Providers/EventServiceProvider.php
に移動し、EventServiceProvider
クラスで保護されたlistenプロパティを見つけます。
protected $listen = [ 'App\Events\ActionDone' => [ 'App\Listeners\ThingToDoAfterEventWasFired', ], ];
コマンドバスにイベントとそのリスナーについて通知するために必要なのはこれだけです。 リスナーの配列があることに注意してください。つまり、イベントには複数のサブスクライバーを含めることができます。
イベントを複数のリスナーから発生させるには、その配列に追加するだけです。
protected $listen = [ 'App\Events\ActionDone' => [ 'App\Listeners\ThingToDoAfterEventWasFired', 'App\Listeners\OtherThingToDoAfterEventWasFired', 'App\Listeners\AnotherThingToDoAfterEventWasFired', ], ];
イベントサブスクライバー
イベントサブスクライバーは、クラス自体から複数のイベントをサブスクライブできるクラスであり、単一のクラス内で複数のイベントハンドラーを定義できます。 上記の例では、EventServiceProvider.php
ファイル内のイベントのリスナーに明示的に名前を付けています。 新しいUserEventListener.php
ファイルでイベントとリスナーを定義します。
サブスクライバーは、サブスクライブメソッドを定義する必要があります。このメソッドには、イベントディスパッチャーインスタンスが渡されます。
<?php namespace App\Listeners; class UserEventListener { /** * Handle user login events. */ public function onUserLogin($event) {} /** * Handle user logout events. */ public function onUserLogout($event) {} public function subscribe($events) { $events->listen( 'App\Events\UserLoggedIn', 'App\Listeners\UserEventListener@onUserLogin' ); $events->listen( 'App\Events\UserLoggedOut', 'App\Listeners\UserEventListener@onUserLogout' ); } }
イベントサブスクライバーを登録するには、EventServiceProvider
クラスに戻り、subscribeプロパティにサブスクライバークラスを追加します。
protected $subscribe = [ 'App\Listeners\UserEventListener', ];
これで、UserEventListener
で作成されたすべてのイベントとリスナーが機能します。
イベントの発送
イベントを発生させるには、Event
ファサードを使用してイベントを発生させることができます
use Event; use App\Events\ActionDone; ... Event::fire(new ActionDone());
または、event
ヘルパーメソッドを使用してイベントを発生させることができます
use App\Events\ActionDone; ... event(new ActionDone());
リスナーにデータを渡すには、イベントクラスをDTOとして使用するだけです。
キューに入れられたイベントリスナー
イベント/リスナーがアプリケーションの処理を遅らせたくない場合があります。 たとえば、ユーザーが新しいアカウントを作成するためのリクエストを送信している間、ユーザーが新しいユーザーのサインアップメールがメールで送信されるのを待つ必要はありません。
アプリケーションキューにイベントリスナーを追加できます。これは、指定したキュードライバーによって処理されます。 これは私たちのアプリの処理を妨げることはありません。 これを行うには、ListenerクラスにIlluminate\Contracts\Queue\ShouldQueue
を実装させるだけです。
<?php namespace App\Listeners; use App\Events\ActionDone; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class ThingToDoAfterEventWasFired implements ShouldQueue { /*...*/ }
リアルタイムでのイベントのブロードキャスト
最近の多くのWebアプリケーションでは、WebSocketを使用して、リアルタイムのライブ更新ユーザーインターフェイスを実装しています。 サーバー上で一部のデータが更新されると、通常、メッセージはWebSocket接続を介して送信され、クライアントによって処理されます。
イベントをブロードキャスト可能にするには、イベントクラスにIlluminate\Contracts\Queue\ShouldBroadcast
を実装させます。
<?php namespace App\Events; use App\Events\Event; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class ActionDone extends Event implements ShouldBroadcast { /*...*/ }
ShouldBroadcast
インターフェイスでは、イベントクラスがbroadcastOn
メソッドを実装する必要があります。このメソッドは、イベントがブロードキャストする必要のあるチャネルの配列を返します。
public function broadcastOn() { return ['action-did-occur']; }
デフォルトでは、Laravelはイベントのパブリックプロパティをシリアル化し、JSONとしてクライアントに送信します。 クライアントに送信される内容をより細かく制御したい場合は、broadcastWith
メソッドをイベントクラスに追加し、JSONに変換するデータを返すことができます。
public function broadcastWith() { return [ 'user' => [ 'name' => 'Klark Cent', 'age' => 30, 'planet' => 'Crypton', 'abilities' => 'Bashing' ] ]; }
現在、Laravelにはクライアントでのデータ消費を支援する2つのドライバー(pusherとsocket.io)しかありません。 この記事の範囲では、プッシャーを使用します。
プッシャーはサービスとしてのWebSocketであり、アプリケーションからサービスにリクエストを送信し、プッシャーはすべてのクライアントにメッセージをブロードキャストします。
プッシャーを使用してクライアントでデータを消費するには、次のようにします。
var pusher = new Pusher('pusher-key'); var channel = pusher.subscribe('action-did-occur'); channel.bind('App\Events\ActionDone', function(data) { console.log(data); });
注:ページにpusher
のJavaScriptSDKが必要です。
<script src="//js.pusher.com/2.2/pusher.min.js"></script>
composer
から入手できるpusher
のPHPSDKも必要です。
composer require pusher/pusher-php-server:~2.0
シャウトボックスの作成
デモアプリケーションはシンプルなシャウトボックスで、ユーザーはフォームにTwitterハンドル、メールアドレス、およびシャウトしたい内容を入力します。
.env
ファイルを構成します。私のものは次のようになります。
APP_ENV=local APP_DEBUG=true APP_KEY=APP_KEY DB_HOST=localhost DB_DATABASE=scotchbox DB_USERNAME=root DB_PASSWORD=password PUSHER_KEY=PUSHER_KEY PUSHER_SECRET=PUSHER_SECRET PUSHER_APP_ID=PUSHER_APP_ID
.env
ファイルとその機能の詳細については、以下をお読みください。Laravel環境変数について
プッシャーは、アプリケーションのリアルタイムコンポーネントを簡単にする優れたサービスです。 これらは有料サービスですが、無料のサンドボックスオプション(最大接続数20、1日あたり10万メッセージ)があり、小さなアプリケーションや開発だけで十分です。 先に進み、 Pusher でアカウントを作成して、資格情報を取得します。
データベースの設定
shoutouts
を保持するためにデータベースを準備しましょう。 migrationとEloquentモデルを使用してshoutouts
テーブルを作成する必要があります。
プロジェクトのルートにあるコマンドラインから次のコマンドを実行します。
php artisan make:migration create_shoutouts_table --create=shoutouts && php artisan make:model Models/Shoutout
職人によって作成された移行(database/migrations/create_shoutouts_table.php
)には、次のものが含まれます。
$table->increments('id'); $table->string('handle'); $table->string('email'); $table->text('content'); $table->timestamps();
モデルには、fillable
およびtable
プロパティが保護されている必要があります
/** * The database table used by the model. * * @var string */ protected $table = 'shoutouts'; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['handle', 'email', 'content'];
ルーティングとコントローラー
次に、ルーティングを設定し、コントローラーの作成に進みます。 app\Http\routes.php
ファイルに、リソースルートを作成します。
Route::resource('shoutouts', 'SiteController');
ルートがマップされたので、職人を使用してSiteController
を作成しましょう。
php artisan make:controller SiteController
このリソースルートは、CRUD操作に必要なルートを含む、アプリケーションのいくつかのルートを自動的に作成します。 作成されたルートを確認するには、次を使用します。
php artisan route:list
シャウトボックスの作成の処理
新しく作成されたSiteController
のstore
メソッドは、シャウトボックスの作成とデータベースへの保存を処理する場所になります。 コントローラのstore
メソッドに、以下を追加しましょう。
<?php namespace App\Http\Controllers; ... use App\Models\Shoutbox; public function store(Request $request) { $validator = Validator::make($request->all(), Shoutout::$rules); /** * Try validating the request * If validation failed * Return the validator's errors with 422 HTTP status code */ if ($validator->fails()) { return response($validator->messages(), 422); } $shoutout = Shoutout::create($request->only('email', 'handle', 'content')); // fire ShoutoutAdded event if shoutout successfully added to database event(new ShoutoutAdded($shoutout)); return response($shoutout, 201); }
クライアント側(JavaScript)で、Pusherに接続し、App\Events\ShoutoutAdded
イベントをリッスンします。
var notifyUser = function (data) { var data = data.shoutout; if (! ('Notification' in window)) { alert('Web Notification is not supported'); return; } Notification.requestPermission(function(permission){ var notification = new Notification('@'+ data.handle +' said:', { body: data.content, icon: document.getElementById('site_image').content }); }; var loadPusher = function (){ Pusher.log = function(message) { if (window.console && window.console.log) { window.console.log(message); } }; var pusher = new Pusher(document.getElementById('pusher_key').content); var channel = pusher.subscribe('shoutout-added'); channel.bind('App\\Events\\ShoutoutAdded', notifyUser); };
クライアントはブロードキャストされたイベントをリッスンし、 Notification API を使用して、現在サイトにいるすべてのユーザーに警告します。 一連のイベントを発生させるために必要なのは、コントローラーで次の行を使用することだけでした。
// fire ShoutoutAdded event if shoutout successfully added to the database event(new ShoutoutAdded($shoutout));
アプリケーションロジックをコントローラーからイベントリスナーに分離する方法がわかります。 それは私たちのアプリケーションを無駄のない、意味のあるものに保ちます。
結論
うまくいけば、これは、イベントがいかに簡単に使用でき、どのようにアプリケーションを合理化できるかを理解するのに役立ちます。
イベントは、アクションが発生したことをアプリケーションに通知します。 これらは誤用されるべきではありません。すべての操作にイベントが必要なわけではありませんが、肥大化したコントローラーから多くのロジックを移動できる場合は、より適切に編成されます。