イベントディスパッチャーとイベントリスナーの関係を整理しながら、SymfonyのEventDispatcherコンポーネントを使って、簡単な実装をしてみようと思います。

主要なクラス

  • イベント
  • イベントディスパッチャー
  • イベントリスナー

イベント

何かしら動作や変更をする(した)タイミングや事象。
こうやって書くと分かりづらいですが、JavaScriptのonClickイベントなどは比較的に目にすることが多いのではないでしょうか。

イベントディスパッチャー

イベントを発行する人。
トリガーとか、エミッターという名前が付くこともあります。

イベントリスナー

発行されたイベントを聞く人。
イベントディスパッチャーがイベントを発行するのですが、それを受けて色々処理をするのがイベントリスナーになります。

主要なクラスの関係

これらが以下の様に絡みます。

  1. イベントが発生する(イベントディスパッチャーがイベントを発行する)
  2. 各イベントリスナーがイベントを受け取り、何かしらの処理をする

こうやって箇条書きにすると、至極当たり前な感じがしますね。
注目すべきところは、「イベントを発行する人」と「イベントを受け取る人」が分かれている点です。

EventDispatcher

つまり

  1. 1つのイベント発生に対して複数の処理を登録することが出来る
  2. 処理は動的に登録 or 削除することが出来る
  3. イベント発生箇所を変更しなくても、イベントリスナーを追加することで処理を拡張することが出来る

といったことが可能になります。

実際にSymfonyのEventDispatcherコンポーネントを利用して、実装してみたいと思います。

Symfony/EventDispatcher

インストールの仕方

Composerで楽々インストール出来ます。

$ composer require symfony/event-dispatcher

使い方

今回は飲み会(パーティ)の幹事役を想定して実装してみました。

仕様としては、
「飲み会の内容について決まったら、参加者予定メンバーに通知したい」
です。

つまり、「飲み会内容決定」というイベントが発生したら、「参加予定者へ通知する」という処理をしたいと思います。

まずはイベントを定義&作成します。

<?php
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;

class DecidePartyEvent extends Event
{
    public $date;
    public $storeName;
    public $entrantsNumber;
    public $price;
}

$event = new DecidePartyEvent();
$event->date = '2015-10-27';
$event->storeName = '地球のやまちゃん(居酒屋)';
$event->entrantsNumber = '25';
$event->price = '2000円';

次に参加者リスナーを定義&作成します。

<?php

class EntrantListener
{
    public function notify(DecidePartyEvent $event)
    {
        echo '飲み会開催のお知らせ' . PHP_EOL;
        echo sprintf('日時:%s', $event->date) . PHP_EOL;
        echo sprintf('会場:%s', $event->storeName) . PHP_EOL;
        echo sprintf('費用:%s/人', $event->price) . PHP_EOL;
    }
}
$entrantsListener = new EntrantListener();

イベントディスパッチャーを作成して、イベントリスナーをセットします。

<?php

$dispatcher = new EventDispatcher();
$dispatcher->addListener('decide.party', [$entrantsListener, 'notify']);

イベントリスナーをセットしただけでは何も起きません。
なので、イベントディスパッチャーにイベントを発生させます。

<?php

$dispatcher->dispatch('decide.party', $event);

結果

飲み会開催のお知らせ
日時:2015-10-27
会場:地球のやまちゃん(居酒屋)
費用:2000円/人

ちなみに、イベントリスナーをセットしてない別のイベントを発生させてみます。

<?php

$dispatcher->dispatch('cancel.party', $event);

何も起きませんでした。
リスナーを設定していないので、正しい動作です。

ここで、新しい仕様を追加します。
「飲み会内容について決まったら、お店の予約も一緒にしてしまいたい」
です。

つまり、「飲み会内容決定」というイベントが発生したら、「参加予定者へ通知する」の他に、同時に「お店の予約」という処理をしたいと思います。

ここでお店リスナーを定義&作成をします。

<?php

class StoreListener
{
    public function reserve(DecidePartyEvent $event)
    {
        echo '予約申し込み' . PHP_EOL;
        echo sprintf('日時:%s', $event->date) . PHP_EOL;
        echo sprintf('席数:%s', $event->entrantsNumber) . PHP_EOL;
        echo sprintf('費用:%s/人', $event->price) . PHP_EOL;
    }
}
$storeListener = new StoreListener();

「飲み会内容決定」イベントで、お店リスナーもイベントが受け取れるように登録します。

<?php

$dispatcher->addListener('decide.party', [$storeListener, 'reserve']);

イベントを発生させます。

<?php

$dispatcher->dispatch('decide.party', $event);
飲み会開催のお知らせ
日時:2015-10-27
会場:地球のやまちゃん(居酒屋)
費用:2000円/人
予約申し込み
日時:2015-10-27
席数:25
費用:2000円/人

参加者への通知の他にお店への予約も同時に行われていることが分かります。

この様に「飲み会内容決定」というイベントが発生する箇所には手を加えずに、処理を追加することが出来るところが、イベントディスパッチャーとイベントリスナーを利用する大きなメリットの一つだと思います。
また、SymfonyのEventDispatcherコンポーネントは、イベントリスナーの登録を関数単位で行えるところも、柔軟性があって使い易いと感じました。

まとめ

イベントディスパッチャーとイベントリスナーは、プログラム言語やフレームワークを問わず色んな所で利用されています。

アプリケーションによって細かい違いはあるかもしれませんが、概念や考え方はほとんど同じなので、一度理解してしまえば役に立つ場面が多いと思います。

Symfony2の中でも様々なところで使われているので、これらの知識をベースに上手く使いこなしていけたらと思います。