Edited at

PSR-14 Event Manager

More than 1 year has passed since last update.

PSR一覧

PSR-5 / PSR-6 / PSR-11 / PSR-12 / PSR-14 / PSR-16

イベントディスパッチャを定義するPSRである、PSR-14 Event Managerのゆるふわ和訳。プルリクは何時でも歓迎。

PSR-14は2017/01/13時点ではDraft段階です。


Event Manager

イベントディスパッチ機能により、開発者はアプリケーションにロジックを突っ込むことが可能になるよ。

多くのフレームワークは、ユーザがわざわざクラスをextendsしたりしなくてもイベントを追加できるイベントディスパッチ機能を実装しているよ。

"MUST"とか"MAY"とかの意味合いについてはRFC2119を参照。


Goal

イベントのディスパッチ処理用のインターフェイスを作るよ。

共通のインターフェイスを持つことで、開発者は多くのフレームワークにまとめて対応できるライブラリを作れるようになるよ。

例として

・権限が無い場合にアクセスやデータの保存を禁止するセキュリティフレームワーク

・よくあるページキャッシュ

・アプリケーション内で実行された全アクションを保存するロガー


Terms


Event

動作したアクションのこと。イベント名に使用可能な文字列は[A-Za-z0-9_.]だけ(MUST)だよ。

単語の区切りに"."を使ってイベント名を付けることが推奨される(RECOMMENDED)よ。

例:'foo.bar.baz.bat'


Listener

EventInterfaceに渡されたコールバックの全て。

リスナーは何らかの結果を返すかもしれない(MAY)し返さないかもしれない。

リスナーを登録するときに優先度を設定することができる(MAY)。

リスナーは登録順ではなく優先度順に実行されなければならない(MUST)。


Components

イベントを管理するためにふたつのインターフェイスを用意したよ。

・イベント自体の情報を保持する、イベントオブジェクト

・全てのリスナーを保持するイベントマネージャ


EventInterface

EventInterfaceはイベントの動作に必要なメソッドを定義しているよ。

イベント名は、リスナーを起動するためのキーワードになるから必須(MUST)だよ。

各イベントは、そのイベントが発動する元となったオブジェクトを含んでいるかもしれない(MAY)よ。

各イベントは、イベント内で使用する追加のパラメータを持っていてもいい(OPTIONALLY)よ。

次のリスナーへの通知を止めるか否かのフラグは実装が必須(MUST)だよ。

namespace Psr\EventManager;

/**
* イベントのInterface
*/

interface EventInterface
{
/**
* イベント名を取得する
*
* @return string
*/

public function getName();

/**
* イベントの発動元を返す
*
* @return null|string|object
*/

public function getTarget();

/**
* イベントに渡されたパラメータを全て返す
*
* @return array
*/

public function getParams();

/**
* イベントに渡されたパラメータのうち引数の値を返す
*
* @param string $name
* @return mixed
*/

public function getParam($name);

/**
* イベント名を設定する
*
* @param string $name
* @return void
*/

public function setName($name);

/**
* イベントの発動元を設定する
*
* @param null|string|object $target
* @return void
*/

public function setTarget($target);

/**
* イベントにパラメータを渡す
*
* @param array $params
* @return void
*/

public function setParams(array $params);

/**
* 次のイベントに通知を続けるか否かを設定する
*
* @param bool $flag
*/

public function stopPropagation($flag);

/**
* のイベントに通知を続けるか否かの設定を取得する
*
* @return bool
*/

public function isPropagationStopped();
}

同じイベントに複数のリスナーが設定されている場合、通常は全てのリスナーが優先度順に実行される。

ただしstopPropagation(true)した場合は、そのリスナーで実行が止まって、その先のリスナーは実行されなくなる。


EventManagerInterface

EventManagerは、イベントに対するリスナーを全て保持しておくよ。

複数のリスナーが登録されている場合、EventManagerは最後に呼ばれたリスナーの結果を返さないといけない(MUST)よ。

namespace Psr\EventManager;

/**
* イベントマネージャ Interface
*/

interface EventManagerInterface
{
/**
* イベントにリスナーを登録する
*
* @param string $event 対象のイベント名
* @param callable $callback リスナー
* @param int $priority リスナーの優先順位
* @return bool 成功したらtrue
*/

public function attach($event, $callback, $priority = 0);

/**
* イベントからリスナーを削除する
*
* @param string $event 対象のイベント名
* @param callable $callback リスナー
* @return bool 成功したらtrue
*/

public function detach($event, $callback);

/**
* イベントのリスナーを全削除する
*
* @param string $event 対象のイベント名
* @return void
*/

public function clearListeners($event);

/**
* イベントを発動する
*
* @param string|EventInterface $event 対象イベント。イベント名もしくはEventInterfaceをそのまま入れてもいい
* @param object|string $target
* @param array|object $argv
* @return mixed
*/

public function trigger($event, $target = null, $argv = array());
}


Event Manager Meta Document


1. Summary

イベントマネージャPSR策定のプロセスを解説するよ。

目的は各決定事項の背景と理由を説明することだよ。


2. Why Bother?

イベントディスパッチ機能により、開発者はアプリケーションにロジックを突っ込むことが可能になるよ。

多くのフレームワークはイベントディスパッチ機能を実装しているよ。


3. Scope

おいここから先書かれてないやんけファック

背景とか理由とか全然説明されてないやんけファック


その他


結局なんなの

全く単語が出てこないけどObserverパターンのようです。

最近のフレームワークではわりと標準装備されているようです。

CakePHP

Laravel

Symfony

AuraPHP

あと、実はPHP本体にもある。Peclだけど。


具体的に使ってみる

ショッピングカートに商品を突っ込む場合、普通に書くとこんなかんじになるでしょう。

    // 商品を取得する

$item = ShoppingItem::get([
'商品ID' => $_GET['id'], '購入点数' => $_GET['number']
]);
// カートに入れる
ShoppingCart::getInstance()->add($item);

この場合、突っ込む側がShoppingItemやShoppingCartのことを実際に知っていないといけません。

密結合が発生してしまいました。

EventManagerを使うとこんなかんじになります。

    // カートに入れるイベントを作成

$event = new Event()
$event->setName('Model.ShoppingCart.Add');
$event->setParams([ '商品ID' => $_GET['id'], '購入点数' => $_GET['number'] ]);
// イベントを登録
EventManager::getInstance()->trigger($event);

ShoppingCartの実クラスをいちいち知っておく必要はなく、カートを更新することができました。

Model.ShoppingCart.Addみたいな名前の取り決めはもちろん必要です。

裏側ではリスナーの登録が必要ですが、そのあたりはフレームワークやプラグインの開発者が行うことなので、利用者側は気にしなくていいところです。

// ShoppingCartクラス内

// 商品追加リスナーを作成
$listener = function($args){
$item = ShoppingItem::get([
'商品ID' => $args['id'], '購入点数' => $args['number']
]);
return $this->add($item);
}
// 商品追加リスナーを登録
EventManager::getInstance()->attach('Model.ShoppingCart.Add', $listener, 99);

なお、\Psr\EventManager\EventManagerInterfaceを使用した実例が全く見当たらないので、本当に上記の書き方が正解なのかはよくわからん。


感想

冒頭の"with the need to extend classes"はどうも"without"の間違いっぽい。

未だになおってないとか大丈夫かこのPSR。

EventManagerInterface::detach()ってどう使うの?

$callback同士を===するの?

一桁後半以降のPSRって全く浸透してないよね。

PSR-2があれほど持て囃されていた時代に比べると隔世の感がある。