Laravelのイベント

タグ: Laravel4  

はい、こちらLaravelコミュニティーふにゃふにゃ担当の川瀬さんです。あまりにもがっちがちのコミュになってしまうと、雰囲気が悪くなります。そのせいでフレームワークまで堅苦しいように思われてしまいますので、程々におちゃらけている、やわらか開発者です。(最近何か開発していたっけ?げっ、開発してないじゃん!)

Laravelのコミュニティーの良い所は、あまりにもガチガチしてなくて、ほどよくほぐれているところです。だらだらになるほどではありません。一本筋をすうっと通す程度の真面目さで、時々はおちゃらけてもOKです。ですから、お気軽に各所(FacebookやG+などのグループ、2chは…あそこは、まあ、もともと独自路線ですね。私はいません。)へご参加ください。(英語のコミュニティーも、硬軟混ざっているようですので、そうした意味で雰囲気が近いんじゃ無いでしょうか。日本人のコミュはとかく真面目すぎて堅苦しくなりすぎることか多いようなので、もっと力抜いたほうがより参加者が増えると思うんですけどね。)

さて、Laravel3の頃から、イベントは既に存在していました。「Laravelのイベントはただの配列だろ?」と何度もバカにするようなツイートがされたものです。

こうした内容をつぶやく人は、OSの中身やネイティブで開発するGUIのMVCの知識を持っている人なんでしょうね。PHPのフレームワークが、スクリプト言語であるPHPを使い、配列以外の何でイベントを処理しろと言っているのでしょうかね?(コレクションも配列のお友達と考えましょう。)

ただの「配列」じゃないかと卑下してしまえば、PHPフレームワークが持っているほとんどの仕組みを十把一絡げに馬鹿にすることができてしまいます。(でもまあ、たとえOSレベルのイベントでも、配列かその親戚で管理していることには、変わらないでしょう。何によって駆動されるかの違いです。)

もちろん、役に立ちますよ。現行バージョンの4.2を元にして説明しています。

オブザーバーパターン

ドキュメントにも書かれていますが、イベントはオブザーバーパターンを実装したものです。

「オブザーバーパターン?おいしいのそれ?」

そうですね。今はプログラミングパターンを勉強しているわけでありません。この記事はLaravelのアドベントカレンダー2014のために書いています。私達は今、Laravelを学習しています。ですから簡単に説明しましょう。

オブザーブとは「監視」です。あるオブジェクトの内容が変わったことを監視し、その変更をあるインスタンスへ通知する仕組みです。もっと分かりやすい言葉では「発行−購読」です。ある事象が起きたら知らせて欲しいと登録しておきます。同じ事象に対して、通知先はいくつも登録可能です。実際に該当する事象が起きたら、登録先へ通知されます。そうした仕組みです。

ご察しの通り、「事象」こそイベントの訳語です。

では、何が嬉しいのでしょうか。これは、機能を独立させ、交換可能にしてくれます。

難しい?ぶっちゃけて言ってしまえば、ある機能を外部から拡張・削除・変更可能にする「プラグイン」とか「拡張機能」を提供するのに便利な仕組みです。

「フック」を提供するための「コールバック」に似ている?そうですね。複数の追加機能を登録できるフックのような仕組みのことです。

機能拡張

なぜ「ぶっちゃけちゃう」かといえば、実際にLaravelのコアの中でも、ユーザー(つまりLaravelを使って開発している私達)に、Laravelの提供している機能を拡張させる仕組みを提供している箇所で、このイベント機構を使用しているからです。

イベントを直接使った拡張の提供という点で分かりやすいのが、Eloquent ORMです。ORMで行う処理の各段階でイベントが発行されます。こうしたイベントを購入(イベントにより起動されるコードを登録すること)し、モデルの振る舞いをモデルの外側から変更することが可能です。

フレームワークの利用者に直接イベントを触らすのが最適でないこともあります。ビューのコンポーサーはイベントを使用して実装されていますが、イベントを直接触らせずに、「ビュー・コンポーサー」という概念を被せています。

もちろん、私達自身が開発する仕組み・機能・クラスの振る舞いを外部から拡張・変更可能にするため、イベントを利用することも可能です。

コアのソースから学ぶ

深く知りたい方は、上記で紹介した「Eloquent ORM」・「ビュー・コンポーサー」の2つと、「Eventクラス」のコードを読んでみると理解が深まるでしょう。Eventファサードの正体は、Illuminate\Events\Dispatcherクラスです。ビューコンポーサーはIlluminate\View\Factoryをご覧ください。Eloquent ORMのイベントのは、Illuminate\Database\Eloquent\Model抽象クラスです。長いクラスですが、'event'で検索をかけてみれば、素直にイベントを利用しているのが理解できます。

Laravelのコアクラスをeventで検索してもらえば、もっと見つかりますよ。

ビューコンポーサー

ビューコンポーサーとは、指定したビューをレンダー(描写すること。LaravelのViewクラスの場合は、インスタンスの内容を文字列にすること)する前に、ビューのプロパティを追加したり、内容を変更する機能です。前述のとおりに実装はイベントを利用していますが、(イベントを知らなくても使える)便利なビューの機能として使ってもらうために、メソッドでラップされています。

実際に使用してみましょう。先ず簡単なビューを作成します。

app/views/blog.blade.php

<body>
    <p>{{ $content }}</p>
</body>

ビューのcontentプロパティーを表示するだけです。(特定のブラウザで表示できないというクレームはご勘弁を。分かりやすいように最小限度のHTMLにしています。)

app/routes.php

<?php

# 表示のためのルート
Route::get( 'blog', function()
{
    return View::make( 'blog' )->with( 'content', 'コンテナの話です。:)' );
} );

# スマイリー機能コンポーサー
View::composer( 'blog', function( $view )
{
    $view->content = str_replace( ':)', '<img src="'.asset( 'smiley/smile.png' ).'" alt="smile">', $view->content );
} );

# リンク追加機能コンポーサー
View::composer( 'blog', function( $view )
{
    $view->content = str_replace( 'コンテナ', '<a href="http://readouble.com/laravel/4/2/0/ja/ioc.html">コンテナ</a>', $view->content );
} );

CMSでよくある機能をシミュレートしています。最初のコンポーサーはスマイリー機能です。特定の文字列を画像に置き換える機能です。ここでは一般的な笑い顔のスマイリー:)public/smiley/smile.pngの画像へ置き換えています。

2つ目のコンポーサーでは特定の文字列にリンクを貼っています。今回は「コンテナ」という文字列にリンクを張っています。最近はみかけませんが、外国のサイトでは今だにこれを多用しており、うざったいページがありますね。でも、初心者向けの技術解説記事では、専門用語に解説記事へのリンクを張ってあげるのは、便利です。

ここではクロージャーでルートロジックを定義しているだけですが、もっときちんとしたシステムを構築済みで、既にコントローラークラスのテストが終わり、実働だったりする場合、直接手を入れたくない場合もありますね。そんな場合でも、コンポーサーでロジックを足したり、変更したりできます。

もし、スマイリー機能が必要なくなれば、コンポーサーの定義を消すだけです。ルートのロジックは変更ありませんし、特定文字列にリンクを張る機能も今まで通り動作します。

Eloquentイベント

まだまだペットブームです。可愛いワンちゃんのために犬クラスを作成しましょう。マイグレーションのひな形をartisan migrate:makeで作成し、dogsテーブルを作成します。

public function up()
{
    Schema::create( 'dogs', function( Blueprint $table )
    {
        $table->increments( 'id' );
        $table->string( 'name' );
        $table->string( 'breed' );
        $table->string( 'sex', 1 );
        $table->timestamps();
    } );
}

ここではup()メソッドしか書いていません。あしからず。

読んで時のごとく、「犬」テーブルなのです。しかし、上司からは犬のように服従を強いられ、それが故に部下から「会社の犬」と罵られる中間管理職さん達は自分のことかと思われるでしょう。それと日本人は英語に弱いですからね。

そこで、この中間管理職の技術者さんが、このテーブルへのロジックを追加するとき、sexフィールドに1とか2が並んでいるのを見て、「これは…週の回数?」と思い込むかも知れません。そこで犬とは自分のことと思い込んでいる彼は、自分のレコードを追加して、見栄を張り、sexフィールドに9を入れ込むかも知れません。(さすがに、一桁の文字なので14とか21とかまでは、見栄を張ることができないことには気付いているようです。なにせ、技術者の端くれですからね。)

もちろん賢い皆さんは、テーブルに制約をきちんと付けることを考えるでしょう。ですが、設計した私は皆さんを信じているのです。sex1なら男性、2なら女性だと黙っていても全員がわかっていて、他の値を絶対入れないと確信しているのです。ですから、制約を付けて皆さんを疑うなんてことや、付けたが故にデータベースエンジンに余計なオーバーヘッドをかけ、アクセスを遅らせることなんてできないのです。(なにせ、スピードは大切ですからね。:P)(もちろん、Laravelが遅いとつぶやいている人へのあてつけです。:D)

しばらくして私の信用を裏切ったこの中間管理職技術者のせいで、システムダウンが引き起こされました。原因を特定するのに丸1日かかり、復旧するまで2日かかりました。クレームの電話は4日鳴り続け、サーバーは大量のメールで8日目にパンクしました。私とこの中間管理職は16%の給与ダウンが申し付けられました。もう散々(33)です。あれ、2の指数から外れたか…

もう動作しているので、ここでシステム止めて制約を貼り直すなんてしたくありません。(また、クレームの電話とメールが山のように来るでしょう。)けど、怠け者で面倒が嫌いな開発チーム全員はなんと、ちゃんとEloquentを利用してコーディングしていました。(面倒くさいのはゴメンだ!)では、今回の教訓を活かし、テーブルに保存する前にsexが12であることを確認するロジックを追加することにしましょう。

ここまでが長い前ふりです。Code Brightばりに長い導入部です。(心の中では、密かにCode Brightよりは面白くてイメージが持ちやすいだろうと自負していますが、そんなことはお首にも出していません。私は奥深しい人間なのです。ところで、Dayle Reesには内緒ですよ。)(すまん、Dayle Reesよ。今日は誕生日なのに。けれど、誰も英文に翻訳しないだろうから、知らないままでいてくれ。日本には知らぬが仏というありがたい教えがあるのじゃ。)

既に完成し、テストも済み、実働しているDogモデルは次の通りです。

class Dog extends Eloquent
{
    protected $fillable = array( 'name', 'breed', 'sex' );
}

こんなクラスでどうやってテストしたのかは謎ですが、とにかくテストが終わっています。もう、触りたくないですね。触っちゃったら、テストを自分で追加しなくちゃなりませんからね。他の人のコードの改修なんてやりたくないのに、おまけにテストまで書かなくちゃならないんですよ。回避しましょう。適当な場所、つまりルートに制御が渡る前のどこかで、Dogモデルのsavingイベントに対する処理を指定しましょう。

Dog::saving( function($dog)
{
    if( $dog->sex !== '1' and $dog->sex !== '2' ) return false;
} );

Eloquentの機能です。イベント名の静的メソッドを定義すると、イベント発生時にリスナーとして処理されます。

これで、sexフィールドが1でも、2でもない場合は、保存せず、保存しようとしているDogの静的メソッド(createとかsaveとか)は、falseをリターンします。もし、他の開発者がこうしたメソッドを使用していて、正しく保存されたかを確認するために、戻り値をチェックしていなくて、ロールバックされないがために、どこかでテーブルの整合性が取れなくなって、バグが出る可能性もありますが、リターン値をチェックしないほうが悪いのですから、気にしないようにしましょう。要は、自分のところだけきちんと動けば良いのです。(開発者単一責任の原則)

注意する点は、Dogクラスの内部に直接ロジックを追加しているわけでないことです。イベント云々を除いて仕様だけ考えてみれば、「コールバックによるフック」が用意されているように見えますね。

でもちゃんとイベントを使っていますよ。だから、Eventクラスを使ってもsavingイベントを捕まえることができます。以下のコードは、直前のDog::savingメソッドと同じ動作をします。

Event::listen('eloquent.saving: Dog', function($dog)
{
    if( $dog->sex !== '1' and $dog->sex !== '2' ) return false;
} );

listenメソッドを使ったこの書き方なら、アスタリスクを使用したワイルドカードも指定できますから、特定のクラスだけではなく、全てのEloquentモデル共通で保存前にチェックしたい内容がある場合、'eloquent.saving: *'で補足できます。

インターフェイスによる契約

Laravelは開発者のレベルを引き上げてくれます。新しい環境や便利な手法を取り入れ、成長し、それを利用者に提示していきます。ですから、Laravelを使い続けている人のレベルはどんどん高くなっていきます。「レッツ、ツギャザーしようぜ」です。その代わりと言っちゃなんですが、今まで使っている開発者のレベルが上がっている分だけ、これから始める初心者への敷居が上がってきてしまいました。(でも、他のフレームワークよりいいんですぜ。なにせ、一部の意識高い人たちが「下々の者、聞くが良い。私達のやり方を学ぶのじゃ。他のやり方など認めん」と初心者をバッタバッタとなぎ倒し…おっといけない。フレームワーク戦争に火を付けるところだったぜ。:D )

次の5.0では、インターフェイスを定義し、それを利用する清く正しいおりこうさんの手法が推奨されてきます。そうです、Laravel開発者のTaylor Otwellもしっかりしたアプリケーションの構築には必要であると、既にずいぶん前から語っています。電子本にも書いていました。それ以降に出版した電子本の中でも「インターフェイスによる制約」の概念は紹介されています。Web上の記事でも書かれています。(英語ですけどね。)

既に5.0の現在の状態(ベータ以前、多分アルファー)のドキュメントは翻訳してあります。そこの「契約」のページに概念が載っています。お読みになった方はなんとなくでもどんなものか理解しているでしょう。Laravel5からは、この手法がプッシュされます。

これに従い、インターフェイスで付けたり取ったりしたい機能をクラスに分けて実装することもできます。

例えば、ある会社の給与計算を考えましょう。こうした例の場合、給与計算が一番です。(正直言えば、別のものを取り扱いと考えたのですが、思いつきませんでした…)

先ず、計算ロジックを表すインターフェイスを定義します。(名前空間省略の方向でお願いします。)

interface processPayrollInterface
{
    public function calculate( Payroll $payroll );
}

calculateメソッドが存在すればよいのです。なぜ社員でなく、給料簿(payroll)クラスなのかは置いといて、そのインスタンスが渡されます。(ずっと置き去りにします。本当に深い意味はありません。適当に始めた結果がこれです。)これが契約です。実際のPayrollはEloquentモデルを直接使ったり、一枚かぶせのリポジトリークラスを採用したりと、色々な手法が考えられますが、何かがなければしょうがありませんので、ダミーでスタブクラスを考えましょう。

class Payroll
{
    public $name;
    public $kihonKyu;
    public $zangyo;
    public $yakan;
    public $payment;

    function __construct( $name, $kihonKyu = 100000, $zangyo = 0, $yakan = 0 )
    {
        $this->name = $name;
        $this->kihonKyu = $kihonKyu;
        $this->zangyo = $zangyo;
        $this->yakan = $yakan;
        $this->payment = 0;
    }
}

とりあえずこれで、Eloquent風にアクセスできます。では次に、基本的な計算クラスを考えます。

class basicProcess implements processPayrollInterface
{
    public function calculate( Payroll $payroll )
    {
        $jikyu = 1000;
        $payroll->payment = $payroll->kihonKyu 
            + $payroll->zangyo * $jikyu * 1.25 
            + $payroll->yakan * $jikyu * 1.5;
    }
}

給料を自由にコントロールするため、配列でクラスを保持し、配列を回してアルゴリズムを実行しましょう。これで、アルゴリズムを付け加えたり、削ったりできます。そのためにコンテナを使いましょう。コンテナには何でも入ります。

App::bind( 'paymentProcessers', function()
{
    return [
        new basicProcess(),
    ];
} );

さて、実際に計算してみます。

Route::get( 'pay', function()
{
    # スタブ(ダミーデータ)生成
    $payroll = new Payroll( '田中', 100000, 100, 20 );

    # これ以下がビジネスロジック
    # 独立したクラスで、本来テストが行われる

    # 給与計算ロジック取得
    $paymentProcessers = App::make( 'paymentProcessers' );

    foreach( $paymentProcessers as $processer )
    {
        $processer->calculate( $payroll );
    }
    # ここまでがビジネスロジック

    return $payroll->name.'さん、今月の給料は'.$payroll->payment.'です。';
} );

ダミーデータを生成し、計算し、出力しています。ビジネスロジックの部分は本来クラス分けたほうが良いのですが、あまりやり過ぎるとチュートリアルでは分かりづらいので省略です。

URLのpayを呼び出してみれば、計算結果が表示されます。

田中さん、今月の給料は255000です。

田中さん、頑張った割には基本給が低すぎて、あまりもらえませんでした。しかし、それに鞭打って、ワンマン社長の鶴の一声が響きます。「不景気だ。円高だ(そうでなきゃ、円安だ)。これじゃ倒産だ。だから、今月から残業は80時間まで!」

80時間で帰れとも、80時間分以上払わんぞとも、意味が取れますが、いずれにせよ80時間が限度になってしまいました。基本給が安すぎるのに加え、ブラック化がレベル2に上がりました。

でも、電算室はちゃっちゃとアルゴリズム追加しちゃいましょう。(こういう会社は日本語の部課名にこだわったりするので、今だに電算室とか言っちゃってます。)頑張ってもお金もらえません。時間内に片付けましょう。新しい処理クラスを作成します。

class blackfilter implements processPayrollInterface
{
    public function calculate( Payroll $payroll )
    {
        if( $payroll->zangyo + $payroll->yakan > 80 )
        {
            $payroll->zangyo = 80;
            $payroll->yakan = 0;
        }
    }
}

イエーイ、残業と夜間を合わせて80越したら、残業だけが80時間分支払われます。

この新しい処理を付け加えます。コンテナに登録した内容を変更します。

App::bind( 'paymentProcessers', function()
{
    return [
        new blackfilter(),
        new basicProcess(),
    ];
} );

連想配列では順番の保証がされなーい!と言う声が聞こえてきそうです。どっこい、PHPの配列は並び順は覚えているので、foreachで回せばこの順番で実行されます。心配であれば明示的に数字のキーを指定し、ソートした結果をreturnすればよいですね。(コンテナに対し、インスタンス化のロジックも指定できます。)

これで実行します。

田中さん、今月の給料は200000です。

あー、やっぱり下がりました。田中さんのモチベーションも下がります。

ここで重要なのは、テストが終わっているだろう、呼び出し側のビジネスロジックには変更がありません。ユニットテストを再度行う必要もありません。(CI採用なら自動的に行われているでしょうが…)

そんな時、更に田中さんのモチベーションを下げるような噂が社内に広まりました。社長は壇蜜とよく似ている秘書の「レイコ」さんと不倫をし、マンションで囲っているとの噂です。社員の給料を叩いておいて、その金でしっぽりです。

それを裏付けるメモがそっと電算室に回ってきました。ええ、メールではなく、付箋紙で他の書類に貼り付けてあります。付箋に部外秘と赤い大きなはんこが付いています。その大きなはんこを押すために、大きな付箋が張られていたのでやたら目立ちます。どうやら、レイコさんは秘書の方の腕前は良くないようです。

「秘書のレイコさんは忙しく、肉体的に疲れているので毎月30万円の手当を別途与えること。社長より」

本来なら部外秘ではない内容なのに、部外秘にするなんて確定的ですね。もう会社を辞めることを考えつつも、仕事は片付けましょう…

class secretOteate implements processPayrollInterface
{
    public function calculate( Payroll $payroll )
    {
        if( $payroll->name == 'レイコ' )
        {
            $payroll->payment += 300000;
        }
    }
}

では、コンテナの登録内容にも付け加えましょう。

App::bind( 'paymentProcessers', function()
{
    return [
        new blackfilter(),
        new basicProcess(),
        new secretOteate(),
    ];
} );

ダミーデータの名前を「レイコ」に変更し、実行してください。

レイコさん、今月の給料は500000です。

さて、これでLaravel5から推奨される(強制ではない)インターフェイスの契約を使用した、柔軟な機能拡張を見てみました。柔軟ですが、この方法ではコンテナ(他の場所で保持しても良い)に登録した配列の内容を変更しなくてはなりません。

関連する部分全部を開発チーム内部で開発する場合であれば、問題ないでしょう。IDEならインターフェイスをimplimentすれば、インターフェイスで定義されているメソッドがないよと教えてくれたり、「じゃあ、作っちゃう?」と用意してくれたりしますから、コーディングが面倒になるわけでありません。特にこのようなやたらに変更がかけられてはまずい部分では、外部にクラスが用意されただけで、機能が取り込まれてしまっては危険ですからね。

ところが、これが逆にCMSなどの機能の場合、CMSユーザーが外部から変更するのにわざわざコンテナに登録する定義内容を登録し直さければならないなんて、いけていません。決まったディレクトリーにクラスを設置するとか、決まったファイルに書いておけば、自動的に取り込まれるくらいにしないと、最近のヤングな開発者の皆さんには受けません。

そんなわけで、機能追加のフックを用意するために自分でコーディングしても良いのですが、せっかくLaravel使うのでしたら、わざわざ車輪の再発明することもないわけです。

それじゃ、まああり得ない「イベント駆動型給与計算CMS」に改造しましょう。(意味が分かりません。:D)

ビジネスロジックとその前後のルート処理です。繰り返しますが、本来ビジネスロジックはクラスに押し込み、きちんとテストしましょう。

Route::get( 'pay', function()
{
    # スタブ(ダミーデータ)生成
    $payroll = new Payroll( '田中', 100000, 100, 20 );

    # 給与計算ロジック
    Event::fire( 'paymentProcessing', [$payroll ] );

    # 出力
    return $payroll->name.'さん、今月の給料は'.$payroll->payment.'です。';
} );

このままでは計算ロジック(拡張機能)がないため、田中さんの給料は0円です。タダ働きです。超ブラックです。ついにブラック化レベル9です。これではこちらも悪事に加担したと思われちゃいます。

では、基本ロジックをイベント購読で追加しましょう。

Event::listen( 'paymentProcessing', function( Payroll $payroll )
{
    $jikyu = 1000;
    $payroll->payment = $payroll->kihonKyu 
        + $payroll->zangyo * $jikyu * 1.25 
        + $payroll->yakan * $jikyu * 1.5;
} );

255,000円もらえるようになりました。社長がブラック指示を出します。80時間以上はサービス残業です。

Event::listen( 'paymentProcessing', function( Payroll $payroll )
{
    if( $payroll->zangyo + $payroll->yakan > 80 )
    {
        $payroll->zangyo = 80;
        $payroll->yakan = 0;
    }
} );

なんと変化しません。それとも、するかも知れません。一体どちらでしょう?

えっ,どっちつかずに思えますか?でも仕方ないんです。この書き方では、イベントの購読順序は保証されているわけでありません。もちろんコアのコードを読めば、先に登録されたほうが優先して実行されるのはわかるんですが、コアが変更されてしまうかも知れませんからね。

それでは不便ですから、リスナーの実行優先順序を明示的に指定できるようになっています。どれを先に実行するか指定しましょう。

Event::listen( 'paymentProcessing', function( Payroll $payroll )
{
    $jikyu = 1000;
    $payroll->payment = $payroll->kihonKyu 
        + $payroll->zangyo * $jikyu * 1.25 
        + $payroll->yakan * $jikyu * 1.5;
}, 1000 );

Event::listen( 'paymentProcessing', function( Payroll $payroll )
{
    if( $payroll->zangyo + $payroll->yakan > 80 )
    {
        $payroll->zangyo = 80;
        $payroll->yakan = 0;
    }
}, 10000 );

listenメソッドの第3引数で優先度を指定します。大きいほど優先で先に実行されます。登録順を交換しても安定して結果が出ます。試してくださいね。

レイコさんのお手当も用意しましょう。

Event::listen( 'paymentProcessing', function( Payroll $payroll )
{
    if( $payroll->name == 'レイコ' )
    {
        $payroll->payment += 300000;
    }
}, 100 );

計算ロジックをクロージャーで書きましたが、もちろんクラスも使えます。(つまり、小さなロジックのクラスですから、簡単にテスト可能です。)詳細はドキュメントで確認ください。

イベントのロジックの中からfalseを返せば、他のリスナーへ実行が移るのを止めて、そのリスナーでおしまいにもできます。

簡単な例でしたが、これで「契約」で縛ってコンテナで機能クラスを配列に突っ込んで回す方法と、イベントで必要があれば実行の優先順位を付けて拡張機能を付ける方法が使える「頭脳」ができたと思います。頭の片隅においておけば、機能追加を自在に行いたいシチュエーションで役に立ちますよ。

もちろん機能拡張してもらう開発者には、特定ファイルにイベントの定義をまとめて指定してもらい、それをサービスプロバイダーとか初期化ファイルで読み込みましょう。もしくは、特定ディレクトリーの中にイベント定義クラスを設置してもらい、それらを読み込みながらイベント登録すれば、もっとCMS風になりますね。

ビューコンポーサーのように、イベントを見せないようメソッドをかぶせたり、ヘルパー関数を提供したりという手も取れます。Laravelにはファサードの仕組みがあります。クラスインスタンスでテストしておいて、Keisan::registerCalculate()とか、Kyuryo::registerPreProcess()とか簡単に提供できちゃいますから。

今回は一つのイベントを一箇所で発行し、それを複数のリスナーで処理しました。一つのイベントを複数箇所で発行するうまい例が思いつかないので、ここでやめておきます。でも、同じイベントが複数箇所で発行されても良いこと、さらに複数のイベントをワイルドカード利用して一つのリスナークラスで受けることもできる点も押さえておきましょう。応用が広がります。自在に処理できます。

イベントはただの配列ではありませんでした。偉大なる配列を讃えましょう。ビバ・イベント!

(さて、こんな記事に呆れ果てたら、来年こそあなたの手でアドベントカレンダーを埋めてください。)