10月2日から3日にかけて、PHPMatsuriに参加してきました。1日目の午前中は大部屋でセッション、午後からは部屋を区切ってセッション+ワークショップの2並列、1日目の夜から2日目の昼まで各自ハック、2日目の午後からハックしたものについてのLT大会、というような流れでした。
事前情報をほとんどチェックできておらず、宿つきのハック大会+ごく小規模なセッションというくらいの認識で行ったのですが、とんでもない勘違いでした。セッションだけでも非常に充実していて、裏側のワークショップも非常に楽しそうで、それだけでヘロヘロなのに深夜までハックする人が多数という、ひょっとしたら通常の4日分くらいが詰めこまれた2日間でした。
僕はずっとセッションを聞いていたのですが、Nate Abeleの「Practical PHP 5.3」とJoel Perras&Nate Abeleの「フレームワーク嫌いの人の為のフレームワーク Lithium」が特に印象に残っています。特に後者はフレームワーク紹介というよりはLithiumの考え方とプログラミング全般の話題が多く、非常に興味深い内容でした。まずはこの2セッションに登場した話題をピックアップして紹介して、最後に僕のハックについても紹介します。
名前空間とPSR-0
NateのセッションではPHP 5.3から導入された名前空間の使い方とメリットに関して詳しく説明していました。これまでのPHPには名前空間が無かったので、例えばsymfony1であればsfから始まるクラス名を付けるなどの工夫で乗り切っていました。PHP5.3からはベンダー名などを名前空間で表現できますので、機能をシンプルに表現したクラス名をつけられるわけです。これはクラスを多く作る人、特にフレームワーク作者の人々には嬉しい内容です。
また、PSR-0*1の説明にも時間を使っていました。PSR-0の素晴らしいところは、名前空間の使い方だけでなく、特定のクラスに対応するファイルパスまで規定していることだと思います。これを皆が守っていれば、他のプロダクトのクラスであっても同じ仕組みでautoloadできます。また、プレゼン中では触れられていませんでしたが、名前空間だけでなくファイルパスもコンフリクトしないことが保証できるので、この点も大きなメリットだと思います。PHP5.3時代には知らないでは済まされない内容であるにも関わらず、僕はこれを初めて聞いたので衝撃を受けました。
途中、「名前空間の区切り文字バックスラッシュが日本のPCだと円マークになるって聞いたんだが本当かい?もし本当ならMacを買うことをお勧めするよ!」というジョークが結構ウケていました。会場のMac率が高すぎたせいもありそうです。
Lambdas & Closures(無名関数とクロージャ)
PHP 5.3から無名関数とクロージャが使えるようになりました。無名関数の本質は、関数を他の変数と同じように扱える(=first-class citizens)ということです。これをうまく表現していたのが、次の例です。
<?php $findActive = function($object) use (&$findActive) { if ($object->isActive) { return $object; } if ($object->children) { foreach ($object->children as $child) { return $findActive($child); } } };
再帰的な無名関数の例ですが、自分自身をuseで受け渡すことで再帰を実現しています。参照渡しでないとうまく動かないコードなのもオシャレな点です。こんな書き方を考えたことがなかったのでプレゼンを見たときは驚きましたが、PHP5.3が普及すればごく普通の書き方になるのかもしれません。
Frameworks and Design patterns Suck
JoelとNateのプレゼンは「suck」連発でした。翻訳者の方は「サイテー」と上品に訳してましたが、僕は次のように聞いていました。
フレームワークは常にクソだ、なぜなら常にオーバースペックだからだ。もちろんLithiumもクソだが、他のフレームワークよりはマシになるよう頑張ってるよ!
各種フレームワークは様々な要求に応えるための仕組みを持っています。使いもしない機能に対応するコードが存在すること、複雑度が増すこと、フレームワークを使っているとこうしたことを当然のこととして受け入れがちですが、ハードウェアリソース、学習コスト、その他の無駄なコストを払っているのは事実です。
また、デザインパターンの話題も何度か出てきて刺激を受けました。話ぶりからすると彼らはPoEAA*2やGoF*3にかなり影響を受けているはずなのに、Martin Fowlerを非難していたのは面白かったですね。
とはいえ、フレームワークの話題と同じく、デザインパターンも万能ではないことは常に心に留めておくべきです。プレゼン中に次のような文章が出てきました。
Each pattern is only useful in a limited context.
これは全くその通りだと思います。拡張性のあるパターンほど複雑になる傾向があり、実効性能も犠牲にしがちです。特定のパターンを採用すべきかどうかには常にトレードオフがあります。
僕の考えでは、彼らが言いたかったことは「クソはクソなりに少しでも良くしなければならない、そのためにLithiumでは色々な工夫をしているんだ」ということだったと思います。
一方で、僕たちへの戒めの部分もあると思います。フレームワークやデザインパターンを盲信せず、常に疑ってかかるべきだ、というのは言われてみれば当然のことなのですが、つい忘れがちです。フレームワークもデザインパターンも銀の弾ではない、それどころかクソだ、という心がけで世界を見ることは(多少ひねくれているかもしれませんが)ステキな考え方だと思います。
DIは万能ではない
プレゼンを聞いているときは完全に誤解していたのですが、LithiumはDI(Dependency Injection)コンテナを採用していないようです。彼らの判断としてDIは重すぎるし不十分だということのようです。
つまり、DIでクラス間の静的な依存性の問題は解決できるけれども、クラスの関係や状態に関する問題は解決できない、これを解決するのはデザインパターンではなく、AOP的なアプローチやreferential transparency(参照透過性)だ、という主張だろうと思います。
Referential Transparency(参照透過性)
参照透過性の説明に一定の時間を使っていたと思うのですが、個人的に理解度が低かった場所です。基本的には、状態に依存しないコードを書くために次のようなものは排除すべきだ、というような話題だったのかと思います。
- $this
- $_GET, $_POST, $_SERVER
- date()
確かに、副作用が無く状態に依存しない関数の方が把握しやすくバグも少なくできるだろうとは思います。ただ、$thisはどうしても必要な場所があると思いますし、副作用のあるコードを一切排除するわけにもいきません。参照透過性を意識して、可能な範囲でコードを分離すべきだ、という内容だったんでしょうか…?このあたりについて記憶がある方、情報を下されば幸いです。
Aspect-Oriented Design(アスペクト指向を取り入れたデザイン)
アスペクト指向プログラミング(以下AOP)は、cross-cutting concerns(横断的関心事、どのドメインにも属さない定型処理というのが僕の理解です)に対するアプローチの一つで、比較的新しい概念です。僕自身は数年前まで聞いたこともありませんでした。AOPによる解決が向いている処理は例えば次のようなものです。
- キャッシュ機構
- ログの書き出し
- アクセスコントロール
こうした処理は複数の異なるクラスに偏在しがちであり、コードクローンを量産してしまうことさえあります。本来、一つのクラスには一つのドメインに関する処理のみを記述すべきであり、こうしたログのオンオフやキャッシュの有無を判定するようなコードをクラス外に書くことで保守性を高められるのではないか、というのがAOPの土台の考えだと僕は理解しています。
これに対し、LithiumではAOP的なアプローチとして、メソッドの挙動を外部から変更するための仕組みが提供されています。プレゼン中の例は次のようなものでした。
<?php use lithium\analysis\Logger; Post::applyFilter('find', function($self, $params, $chain) { Logger::write('info', 'some debug message...'); return $chain->next($self, $params, $chain); });
このように記述することでPostクラスの外部からPost::find()の挙動を変更でき、PostクラスからLoggerに関する記述を排除できます。
プレゼンを聞いているときは全クラスの全メソッドの挙動を外部から変更できるのかと思ったのですが、lithium\core\StaticObject::_filter()もしくは\lithium\core\Object::_filter()を利用しているメソッドの挙動だけを変更できるということのようです。このあたりの詳細は新原さんの記事「Lithiumのフィルタシステム」で紹介されています。
使い方が難しい部分もありそうですが、意欲的な取り組みだと感じました。
Consistency(一貫性)
フレームワークにとって一貫性は大事だ、一貫性を確保することで学習コストは下がる、というような話題も結構時間を使って説明していました。特に、Lithiumの全てのクラスのコンストラクタ引数が$configという配列一つだけ、という決まりは会場でも支持していた人が多かった気がします。
この単語を重視していることからも彼らのパラノイアっぷりが見えるようで、非常に好感を持ちました。
自分のハックについて
セッションを全部聞いていたら23時を回っていて、そこからハックを開始しました。
僕はPHP文法を構文解析して構文木の全ノードをフック可能にするような、PHP上で動く使いやすいPHPのパーサー(構文解析器)を作りました。構文木というのは、構文解析の際に採用したルールと解釈順を木構造に書き下したものです。一般に、プログラミング言語はそれぞれのルールに基づいてプログラムの解釈を決定しますが、その解釈の流れをわかりやすく図示したものだと言えます。PHPの構文木の例を示します。
コアになるパーサークラスはid:moriyoshiさんが「kmyaccのPHP対応パッチをリエントラントにしたよ」で作った、PHPクラスとしてパーサを出力するようなKMyaccのパーサプロトタイプファイルをを利用しています。PHPのCソースコード中のzend_language_parser.yを元に、PHPの構文規則をPHP版yaccファイルの形で作成し、kmyaccコマンドに与えればPHPでPHPを解釈するクラスの完成です。
ここまでは割と単純作業なのですが、かなりとっつきにくい上に理解していても面倒なので、もう少し楽に取り扱えるような工夫をしました。僕個人の目標としてはFindBugsのようなツールを作ろうとしているのですが、PHPのソースコードを解析するような処理全般が書きやすくなると思います。
この工夫の詳細を紹介すると、構文木のノードを意味するクラスのコンストラクタにSymfony Event Dispatcherを組み込んで、全てのルールおよびトークンについてイベントを発行するようにしました。このイベントのリスナーを設定することで、例えばif文、関数呼び出し、変数への代入といった、特定の文法を発見するたびに毎回呼ばれるようなメソッドを外部から簡単に設定できます。
具体的には、下記コードのようにして利用できます。
<?php require_once(dirname(__FILE__).'/libs/PhpLexer.php'); require_once(dirname(__FILE__).'/libs/PhpParser.php'); require_once(dirname(__FILE__).'/libs/EventDispatcherHolder.php'); require_once(dirname(__FILE__).'/libs/ExampleListener.php'); $input_file = $_SERVER['argv'][1]; $listener = array('ExampleListener', 'listener') EventDispatcherHolder::getDispatcher()->connect('start', $listener); $l = new PhpLexer(); $l->setPhpSource(file_get_contents($input_file)); $p = new PhpParser(); $p->yyparse($l);
上記の例ではstartという最上位のルールをフックしています。具体的には、EventDispatcherHolder::getDispatcherでsfEventDispatcherのインスタンスを取得し、connectメソッドでリスナーを設定しています。
イベント名はPHP本体のyacc文法のルール名またはトークン名になっています。現状で利用するにはyaccおよびPHP文法に関する知識が必要ですが、そこまで難しいものではありませんし、典型的なサンプルを提供できれば解決すると考えています。
この内容について、PHPMatsuriの2日目にLTをしました。
- 発表資料:「How to get pure PHP language parser」(PDF)
発表としては、KMyaccがあれば好きなパーサをPHPで作れるよ、という話題にしました。発表時点ではSymfony Event Dispatcherが組み込めておらず、あまり話せる内容が無かったというのが正直なところです。この週末に少しハックを続けて一段落というところまで来ました。
まだ改善すべき点が多数ある状態ですが、現時点の内容でgithubにリポジトリを作ったので、興味のある方は眺めてみてください。サンプルとして上で示したPHPの構文木をDOT言語で出力するプログラムが付属していますので、Graphvizがあれば好きなPHPプログラムの構文木が描けるはずです。
まとめ
他のセッションやLTも刺激を受ける内容ばかりで、本当に充実した2日間でした。また、僕は参加しなかったのですが、コアデベロッパーの方と一緒にコードを書く経験なんて滅多に無いわけですから、ワークショップも行っておけば良かったなあと少し後悔をしていたりします。とにかく贅沢な集まりでした。参加者の方々のブログ記事が「感想ブログ、写真 » PHP Matsuri」にまとまっていますので、あわせて読むとより雰囲気が伝わると思います。
今回のPHPMatsuriは比較的高額な有料イベントということで、開催前はうまくいくかどうか不安もあったかと思いますが、大成功といえるのではないでしょうか。これも安藤さんをはじめとするスタッフの方々の頑張りのおかげだと思います。本当にありがとうございました。
*1:http://groups.google.com/group/php-standards/web/psr-0-final-proposal。僕の記事「PSR-0 を和訳してみた」も参考になるかと思います。
*2:『Patterns of Enterprise Application Architecture』の略称。日本語訳は『エンタープライズ アプリケーションアーキテクチャパターン』。
*3:『Design Patterns: Elements of Reusable Object-Oriented Software』のことを、作者4人のニックネーム(?)にちなんでGang of FourまたはGoFと呼ぶことがあります。日本語訳は『オブジェクト指向における再利用のためのデザインパターン』。