【Laravel】 イベント使って処理を実行する

イベントを利用することで、イベントを送信 → 複数の場所で受け取り といった事が可能になります。イベントを使用することでコントローラとクラス間の疎結合化ができ、スマートに管理できます。

イベントの構造

イベントでは、event関数を用いて送られる情報に応じて、どのクラスに情報を送信するかを定義し、event関数で送信すれば自動的に情報を送ってくれるという動きをします。

イベントの管理をしているのは、app/Http/Providers/EventServiceProvider.phpです。ここの$listenで、どのイベントが来たらどのクラスにイベントを送信するかが定義されています。

laravel what is event

イベントを動かす

イベントを作成し、実際に動作させてみましょう。

イベントとリスナーを作って紐付ける

まずはイベントを受け取るリスナーを作りましょう。artisanで作るのが早いです。

event:generateで作る

先にイベントのクラスと情報の送信先を書いておけば、自動で生成してくれます。ここで、useも書いておかないと全部Providersディレクトリ内に作られてしまうので、存在しないクラスですがuseしておきましょう。

app/Http/Providers/EventServiceProvider.php
// ...
use App\Events\MyEvent;
use App\Listeners\MyListener;
use App\Listeners\MyListener2;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        // event関数でMyEventを発火したら、MyListenerとMyListener2に送信される
        MyEvent::class => [
            MyListener::class,
            MyListener2::class,
        ],
    ];

    // ...
}

定義したら、作成します。

php artisan event:generate

実行すると、app/Eventsディレクトリ内とapp/Listenersディレクトリ内にファイルが作成されているはずです。

useを忘れると、全てProvidersディレクトリ内に作成されます。

make:eventとmake:listenerで作る

上ではProviderに定義してから作りましたが、定義する前に作りたい場合や、イベントとリスナーを1つ1つ作りたい場合、make:eventmake:listenerを使用します。

php artisan make:event MyEvent
php artisan make:listener MyListener --event=MyEvent
php artisan make:listener MyListener2 --event=MyEvent

イベントに値を入れられるようにする

イベントの流れでは、実質クラスのインスタンスを受け渡しするだけです。そのため、イベントのクラスに適当に値を保持できるようにしておきましょう。

app/Events/MyEvent.php
class MyEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    private $value;

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

    public function getValue()
    {
        return $this->value;
    }
}

Dispatchableは、MyEvent::dispatch()等をするためのものです。

SerializesModelsは、モデルの受け渡しであったほうが良いものです。キューを使用するリスナーなどでモデルをシリアライズする必要がある場合、適切にシリアライズしてくれます。

デメリットがあるわけでもないので、そのままで大丈夫です。

受け取る側を実装する

作成したListenerを見ると、handle($event)関数があります。イベントはこのメソッドに渡されます。そのため、実行したい内容はこのhandleメソッドに書きます。

app/Listeners/MyListener.php
use App\Events\MyEvent;
use Illuminate\Support\Facades\Log;

class MyListener
{
    // ...

    public function handle(MyEvent $event)
    {
        Log::debug($event->getValue());
    }
}

イベントを発火する

これで、イベントとリスナーの紐付けと、イベントの内容と受け取る側の定義が完了しました。早速イベントを送信してみましょう。

送信するには、event関数か、MyEvent::dispatchを用います。コントローラに適当に実装してみましょう。

use App\Events\MyEvent;

class HelloController extends Controller
{
    public function sampleStore()
    {
        Log::debug('イベント送信開始');

        // どちらでも良い
        event(new MyEvent('foobar'));
        MyEvent::dispatch('foobar');

        Log::debug('イベント送信完了');
        return 'hoge';
    }
}

実際にページにアクセスするなどしてコントローラを動かしてみましょう!

イベントディスカバリー

今回の例では$listenにイベントとリスナーの対応を手動で登録していました。しかし、自動的に紐付ける方法もあります。

その場合、EventServiceProvider側でイベントディスカバリーを有効にし、handleまたは__invokeメソッドの第1引数にタイプヒントを付けるだけです。

class EventServiceProvider extends ServiceProvider
{
    // ...

    public function shouldDiscoverEvents()
    {
        return false;
    }
}
class MyListener
{
    /**
     * MyEvent $event のように、型をつける
     */
    public function handle(MyEvent $event)
    {
        // ...
    }
}

すでに$listenerに登録されているものに加えて、自動検出したものも実行されます。

そのため、リスナーのhandler__invokeにタイプヒントを付けている場合は、そのリスナーを$listenerに登録してしまうと重複実行されることになるので注意が必要です。

リスナーが増えるとだんだん重くなってきます。そのため、本番環境ではデプロイ時にphp artisan event:cacheを実行してキャッシュを作成しておくことをオススメします。

キャッシュの上書きは同じコマンドで、キャッシュをクリアしたい場合はphp artisan event:clearをします。

イベント登録の種類

上の例では$listerに追加しましたが、それ以外の方法でも追加できます。

Event::listenを使う

EventServiceProviderのboot()内で、Event::listen(Event::class, [Listener::class, 'handle'])のように書くと、イベントとリスナーの対応を追加できます。handleは、呼び出したいメソッドです。

または、リスナーに対応する関数を直接入れます。

class EventServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Event::listen(
            MyEvent::class,
            [MyListener::class, 'handle'],
        );
        
        Event::listen(
            MyEvent2::class,
            function (MyEvent2 $event) {
                Log::debug('event2');
            }
        );
    }
}

ここで、関数を直接入れた場合、タイプヒントをつければ自動的にイベントとの紐付けを作成してくれるため、関数だけでも動作します。

Event::listen(
    function (MyEvent2 $event) {
        Log::debug('event2');
    }
);

イベントを使うと何が良いのか

どのようなイベントでも、全部コントローラにコードを並べてしまうことも可能です。ではイベントを使うと何が良いのでしょうか。

  • コントローラやサービスがスリムになる
  • 同じイベントを使用した機能追加や変更時の影響を最小限にできる

1つ目はコードの所在が変わることによるものです。コントローラやサービスに書いていた処理をリスナーに移すことになります。

2つ目は疎結合化によるものです。普通に書くとコントローラが直接処理部分のメソッドを呼び出したりします。 例えば、今までEmailで通知していたのをSlackへの通知に切り替えたとします。通常であればメソッド名の変更で大量のコードが書き換わるかもしれません。そうするとテストが大変です。置換ミスもあるかもしれません。 しかし、イベントを経由することで、呼び出すリスナーを変更するだけで済みます。つまり、影響をProviderだけに留めることが可能になります。また、slackとemail同時に送信することも簡単に改修可能です。

まとめ

Laravelのイベントはオブザーバーパターンの1つです。イベントを送信し、リスナーがそのイベントを受け取ります。イベントとリスナーの紐付けはEventServiceProviderで行います。

次はキューを使用したイベントも見てみましょう。

laravel event thumb2

役に立ったらシェアしよう!