Laravelでイベントとリスナーを使ってみる

#イベントとは
プログラムから何らかの操作があった際などに処理を実行する仕組です 
イベントを発行することでイベントクラスを呼び出すことができ
イベント自体に処理はなく、イベントでは主に値を扱いその値をリスナーへと受け渡します

#リスナーとは
発生したイベントを受け取りイベントの発生を監視しているのがリスナー
リスナーで実際に処理が行われ
リスナーはイベント毎に作成し登録する必要があります

#イベントを利用する方法
イベントを利用するには、イベントクラスとそれに対応するリスナークラスを作成すし登録する必要があり
そして、必要に応じてイベントを発行すればそのイベントを受け取るイベントリスナーの処理が実行される仕組みになります

#イベントとリスナーを作成する
イベントクラスとイベントリスナークラスを作成するには
`providers`の`EventServiceProvider`を利用します
`EventServiceProvider.php`には`$listen`と`boot()`メソッドが用意されています
イベントクラスとイベントリスナークラスの作成には`$listen`を使用します

```EventServiceProvider.php
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        'App\Events\ItemEvent' => [
            'App\Listeners\ItemEventLister'
        ]
    ];
```
今回はこのように記述しておきます
次に、下記コマンドを実行します

```
$ php artisan event:generate
```
このコマンドの実行により、`$linten`に登録したイベントクラスとイベントリスナークラスが自動的に作成されます
イベントクラスは`app/Events`ファオルだに作成され
イベントリスナークラスは`app\Listners`フォルダに作成されます

それでは実際に作成された２つのファイルの構成を見ていきます

#イベントクラスの構成
`ItemEvent`クラスは下記のようになっています

```ItemEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ItemEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct()
    {
       //
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}
```

イベントクラス内には様々なトレイトが用意されているのと
またメソッドは`__construct()`と`broadcastOn()`の二つのメソッドが用意されていますが
基本的には、イベントは値を扱うのがメインになりますので`__construct()`以外はほとんと使用しません
忘れてはいけないのは、イベントクラスは値を扱うのが仕事なので処理などは必要ないです

次に、イベントリスナークラスの構成を見てみます

#イベントリスナークラスの構成
`ItemEventLister`クラスは下記のようになっています

```ItemEventLister.php

use App\Events\ItemEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class ItemEventLister
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  ItemEvent  $event
     * @return void
     */
    public function handle(ItemEvent $event)
    {
        //
    }
}
```
イベントリスナークラスには`handle()`メソッドと`__construct()`の二つが用意されています
リスナーは処理をする役割があるので、処理を記述するのが`handle()`メソッドになります
引数にはイベントクラスのインスタンスが渡されており、この引数からイベントで管理していた値にアクセスすることができます
また、この`handle()`メソッドが、イベント発生時に呼び出されます

次にイベントを発行するにはどうすれば良いのかです

#イベントの発行
イベントを発行するのは簡単で下記のようにすることでイベントを発行できます

```
event(new ItemEvent(値));
```

ここまでで、イベントクラスとイベントリスナークラスの作成とイベントの発行方法など基本を説明しました
次にこれからを使って実際にイベント発行から値を受け取り、リスナーで処理を実行してみるサンプルを作成してみます

#サンプル機能の作成
前での説明で使用した際に作成した`ItemEvent`クラスと`ItemEventLister`クラスを引き続き使っていきます
まずは今回のサンプルで使用する`Item`モデル、シーダ、マイグレーション、コントローラーを作成していきます

```
$ php artisan make:model Item --all
```

これでモデル、シーダ、マイグレーション、コントローラーが作成できました
最初にマイグレーションの記述を行います
下記のようにしてください

```create_items_talbe.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('items');
    }
}
```
マイグレーション実行します

```
$ php artisan migrate
```

次にシーダファイルを下記のように修正

```ItemSeeder.php
<?php

use Illuminate\Database\Seeder;

class ItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $param = [
            'name' => 'りんご'
        ];

        DB::table('items')->insert($param);

        $param = [
            'name' => 'ばなな'
        ];

        DB::table('items')->insert($param);

        $param = [
            'name' => 'ぶどう'
        ];

        DB::table('items')->insert($param);
    }
}
```
シーダを実行します

```
$ php artisan db:seed
```
これでitemsテーブルに3件おダミーデータが用意できたかと思います

次にコントローラーを作成します
ここでイベントを使用します
`ItemController`が作成されていると思いますがリソースコントローラーなので今回は使わずに新たに作成します

```
$ php artisan make:controller MainController
```
`MainController`を修正していきます

```MainController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Item;    //Itemモデルをuse
use App\Events\ItemEvent;    //ItemEventクラスをuse

class MainController extends Controller
{
    public function index()
    {
        return view('main.index');
    }

    public function item(Item $item)
    {
        event(new ItemEvent($item));    //イベント発行

        return redirect()->route('index');
    }
}
```

上記のコントローラーの`item()`アクションでイベントを発行する処理をしています

次にルートの定義をしていきます

```web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/item}', 'MainController@item')->name('item');
Route::get('/', 'MainController@index')->name('index');
```

2つ追加できました

次に`resources`に`main`フォルダを作成し`index.blade.php`を作成してください
中身はこんな感じにしておきます

```index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ユーザー表示</title>
</head>
<body>
    イベントとリスナー
</body>
</html>

```


ここまでで必要な準備が終わりです
イベントも発行してるのであとはそのイベントの処理を用意するだけになります

`ItemEvent`を修正していきます

```ItemEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ItemEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $item;

    public function __construct($item)
    {
        $this->item = $item;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}
```

`public $item`としてItemのインスタンスを保管しておくプロパティを用意しています
`__construct`で引数で渡された$itemを利用してます
この引数の`$item`は`MainController`の`item()`アクションでイベント発行してる際に引数で渡されている値です
この部分です`event(new ItemEvent($item))`
ここで受け取ったItemの情報をイベントに渡してイベント側でそれを値として扱っています

次にイベントリスナークラスの修正をします

```ItemEventLister.php
<?php

namespace App\Listeners;

use App\Events\ItemEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Storage;

class ItemEventLister
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  ItemEvent  $event
     * @return void
     */
    public function handle(ItemEvent $event)
    {
        Storage::append('item_accesslog.txt', '[Item]' . now() . '' . $event->item->name);
    }
}
```
簡単に説明すると、`item_accesslog.txt`というファイルに`[item]+ 現在の時間 + ユーザーの名前`の形で
書き出すということを処理しているリスナーになります
ちなみに、`item_accesslog.txt`ファイルは作ってませんがそのファイルがなければ自動的に`storage/app`に作成されます

これでサンプルの実装は完了です
ブラウザから`/1`とURLを叩いてみてください
そうすると、`storage/app/item_accesslog.txt`に`[Item]現在の時間 りんご`と書き出しされているかと思います

一連の流れを簡単に説明すると

- URLで指定された番号のItemの情報が`MainController`の`item()`アクションの引数で渡される
- `item()`アクションの中のイベント発行の際にその値を渡してあげる
- その値を受け取ったイベントクラスである`ItemEvent`がその値を`__construct`を使って保管している
- そのイベント発行により呼び出されたイベントリスナー`ItemEventLister`の`handle()`メソッドが処理を実行してファイルに書き出しを行っている

こんな感じです
これが一連のイベント作成からイベント発行しリスナーに処理をさせるまでになります

またイベントをキューに登録して実行させることもできます

#キューを利用する
発行したイベントをキューテーブルに登録し処理したい場合は
イベントリスナークラスにてインターフェースを実装するだけで利用ができます
今回の場合だと`ItemEventLister`クラスになります

```ItemEventLlister
namespace App\Listner;

class ItemEventLister implements ShouldQueue
{
    //省略
}
```