FuelPHP advent Calendar 2013の10日目です。昨日はAspectMockでFuelPHPのアプリを100%テスト可能にする(@kenji_s)でした。
今日は、「イベント機能を使ってアプリケーションをカスタマイズする」です。
イベントとは何か
イベントとは、FuelPHPコアを書き換えることなく、独自の処理を差し込むことができる仕組みです。
- FuelPHPの処理の途中に、トリガー(ここを通ったときに、追加処理を実行する場所)がいくつか用意されている
 - トリガー名を指定して、独自の処理を追加する
 - 独自の処理が追加されたトリガーのところで、独自の処理が実行される
 
という仕組みです。WordPressを使っている方であれば、「WordPressのアクションフックに似ている」と思うかもしれません。
イベントを実行: 処理途中の各トリガーの所では、登録されているイベントがあれば実行する

コードは点線部分で分離されている: アプリケーション自体のコードに手を入れずに、機能追加/変更する

独自の処理を追加する
2通りの方法が用意されています。
app/config/event.phpに追加する方法Event::registerを使う方法
1.は、app/configにevent.phpに書く方法です。詳細はEvent クラス(FuelPHP1.7)をごらんください。
2. は、registerメソッドを使う方法です。今回はこちらを使います。Event クラス(FuelPHP1.7)では、user_loginにClass::method処理を追加する例が掲載されています。
Event::register('user_login', 'Class::method');
bootstrap.phpに書けば、トリガーにフックできます。
トリガーにフックしてみる
では、実際にトリガーにフックしてみましょう。ここでは、Novius OSにフックする例を紹介します。Novius OSは、FuelPHPベースのCMSで、jQuery UI、Wijmoを使った使い易いインターフェースが特徴です。Novius OS Chiba2のイベント一覧にあるadmin.loginFailを使い、ログイン失敗をログに記録します。
/**
 * Copyright (c) 2013 Fumito MIZUNO
 * License: MIT 
 * http://opensource.org/licenses/mit-license.php
 */
Event::register('admin.loginFail', 'warning_on_loginfail');
function warning_on_loginfail()
{
    $message = 'Login Fail.';
    $message .= '  Email: ' . Input::post('email');
    $message .= '  Password: ' . Input::post('password');
    $message .= '  IP: ' . Input::ip();
    Log::warning($message);
}
Novius OSは、メールアドレスとパスワードでユーザー認証するので、ログイン失敗時にそれらを記録します。これらはFuelPHPのInputクラスを利用します。引数無しでInput::post()を実行すると$_POSTを全部取ってきて配列で返すので、丸ごと記録したければFormat::forge(Input::post())->to_serialized()としてもいいでしょう。またアクセス元のIPアドレスも記録し、ログ記録にはFuelPHPのLogクラスを利用します。ログ記録の内部処理はMonologライブラリ(MITライセンス)が行っています。
ログインに失敗すると、ログファイルに
WARNING - 2013-12-09 09:15:23 --> Login Fail.  Email: email@example.com  Password: p@ssw0rd  IP: 127.0.0.1
のように記録されます。
デフォルトで用意されているイベント
Event クラス(FuelPHP1.7)によると、デフォルトで用意されているイベントは、app_created、request_created、request_started、controller_started、controller_finished、response_created、request_finished、shutdownです。これらのトリガーに処理を追加することができます。
アプリケーションにトリガーを用意する
コアに用意されているトリガーを使うだけではなく、アプリケーションにもトリガーを用意することができます。アプリケーションの処理の流れ: 青丸がトリガーの、青丸を作る作業です。コード中の適当な箇所に、Event::trigger(トリガー名)と書けばOKです。
Novius OS のログイン部分のコード(novius-os/framework/classes/controller/admin/login.ctrl.php)を見てみると、ログイン成功時にadmin.loginSuccessイベント、ログイン失敗時にadmin.loginFailイベント、が用意されています。
/**
 * NOVIUS OS - Web OS for digital communication
 *
 * @copyright  2011 Novius
 * @license    GNU Affero General Public License v3 or (at your option) any later version
 *             http://www.gnu.org/licenses/agpl-3.0.html
 * @link http://www.novius-os.org
 */
namespace Nos;
class Controller_Admin_Login extends Controller
{
// 途中省略
    protected function post_login()
    {
        if (NosAuth::login($_POST['email'], $_POST['password'], (bool) Input::post('remember_me', false))) {
            if (Event::has_events('user_login')) {
                Log::deprecated('Event "user_login" is deprecated, use "admin.loginSuccess" instead.', 'Chiba.2');
                Event::trigger('user_login');
            }
            Event::trigger('admin.loginSuccess');
            return true;
        }
        Event::trigger('admin.loginFail');
        return __('These details won’t get you in. Are you sure you’ve typed the correct email address and password? Please try again.');
    }
}
どういうときに役に立つのか
アプリケーションを全て自分で作っている場合は、わざわざイベントトリガーを用意するメリットはあまりないかもしれません。トリガーを使わずに書いても良いでしょう。トリガーが威力を発揮するのは、パッケージやアプリケーションをオープンソースで公開する場合だと思います。
Novius OSのログイン失敗時のログを取りたい場合、上述のpost_loginメソッドを直接書き換えるというやり方でも、ログ機能を追加することはできます。でもこの方法だと、元のアプリケーション(Novius OS)のアップデート時に書き直しすることになります。
では、イベントを活用した場合はどうでしょう。Novius OSではトリガーが用意されています。カスタマイズしたい人は、トリガーにフックするコードを自作し、bootstrap.phpに記述します。この方法だと、アプリケーションのコードに手を入れることなく、処理を追加することができます。自分で追加した部分は元のアプリケーションのコードから分離でき、アップデートが楽になります。
もちろん、イベントを使ってカスタマイズするには、トリガーが設定されていなければなりません。なので、パッケージやアプリケーションを公開して、他の人にも使ってもらいたい、という場合には、イベントトリガーを設定しておくと良いでしょう。
まとめ
イベントを使うことで、フレームワークやアプリケーションのコードに手を入れることなく、処理を追加することができます。オープンソースで公開するアプリケーションには、イベントのトリガーを用意しておくと、カスタマイズしやすくなります。
明日は、「FuelPHP をもっと Composer で使う」(chatii0079)です。お楽しみに。
