Lithiumコアライブラリのフィルターを見てみた
昨日の第49回PHP勉強会@関東 - events.php.gr.jpで、@yandoさんがLithiumの現状について発表されたそうで、そのスライドを見てみました。
LithiumはCake PHPの流れを汲むフレームワークですが、PHP 4対応を捨てて先進的な機能を盛り込んでいるので、symfony使いな私でも気になりまくりなフレームワークです。
で、昨日のスライドの中で、コアライブラリの機能を拡張する「フィルター」の話がありました。(スライド25、26ページ)
簡単に言うと、LithiumコアのObjectまたはStaticObjectクラスの派生クラスで、特定の様式(フィルターチェイン方式)
で実行されているメソッドは、applyFilterメソッドでクロージャを設定することでメソッドの動作をカスタマイズできる、ということです。
・メソッドのフィルターを追加するapplyFilterメソッド
http://rad-dev.org/lithium/source/libraries/lithium/core/Object.php
<?php public function applyFilter($method, $closure = null) { foreach ((array) $method as $m) { if (!isset($this->_methodFilters[$m])) { $this->_methodFilters[$m] = array(); } $this->_methodFilters[$m][] = $closure; } }
・フィルター様式でメソッドを実行する_filterメソッド
http://rad-dev.org/lithium/source/libraries/lithium/core/Object.php
<?php protected function _filter($method, $params, $callback, $filters = array()) { list($class, $method) = explode('::', $method); if (empty($this->_methodFilters[$method]) && empty($filters)) { return $callback->__invoke($this, $params, null); } $f = isset($this->_methodFilters[$method]) ? $this->_methodFilters[$method] : array(); $items = array_merge($f, $filters, array($callback)); return Filters::run($this, $params, compact('items', 'class', 'method')); }
この_filterメソッドでは最終的にFilters::run()が実行されています。Filtersクラスはこのようなフィルターチェーンの実行用のユーティリティクラスで、パラメーターで渡された複数のクロージャに対してその場でチェーン(コレクション)を生成して実行します。
- Chain of Responsibilityパターンっぽい実装のようです
実際にフィルター様式でメソッドを実行している例:MySQLアダプターの_executeメソッド
http://rad-dev.org/lithium/source/libraries/lithium/data/source/database/adapter/MySql.php
<?php protected function _execute($sql, $options = array()) { $defaults = array('buffered' => true); $options += $defaults; $params = compact('sql', 'options'); $conn =& $this->_connection; return $this->_filter(__METHOD__, $params, function($self, $params, $chain) use (&$conn) { extract($params); $func = ($options['buffered']) ? 'mysql_query' : 'mysql_unbuffered_query'; $resource = $func($sql, $conn); if (!is_resource($resource)) { list($code, $error) = $self->error(); throw new Exception("$sql: $error", $code); } return $resource; }); }
symfonyではEventDispatherの機能があって、notify型、notifyUntil型、filter型という3種類のイベントがありますが、Lithiumのフィルターはこれらとは少し違いますね。
チェーンの各要素(クロージャ)自身が次の要素を呼び出します。次の要素を呼び出すパラメーターを加工することもできます。また、次の要素を呼び出さずにチェーンを終了するということもできます。(できると思います Chain of Responsibility的に)
「次の」という部分ですらチェーンの要素自身の問題なので、ここを上手くコントロールするとトリッキーなこともできそうですね。
(時間ができたら試してみたいと思います)
というわけで、ソースを眺めただけで実際に動かしたりしていないのですが、この部分だけをとってもLithiumよくできてるな!と思いました。Symfony 2とはまた違った面白さがありますね。