初めてアドベントカレンダーに参加したのですが、いきなり
内容が被ってしまいました。
別のトピックを考えたのですが、思いつかず。まぁ実装が違うので、このままで参加することにしました。別ネタを思いついたので、そちらを書きました。
◆
Underscore.phpという面白いクラスが紹介されてました。
なんでもはunderscoreというjavaScriptをPHPにポートしたものだそうです。何が面白いって、
jQueryみたいにPHPコードが書ける。
早速ソースコードを追いかけてみたのですが、すぐ理解できなかったので自分で書き直してみました。なるほど、分かってしまえば、単純なことを丁寧に積み重ねていたんですね。
理解のために書いたコードですが、せっかくなので、衆目の下に晒すことにしました。クラス名は
_ww。アンダースコア・ワラワラと読んでください。
ちなみにWalla Wallaはアメリカ合衆国はワシントン州にある小さな町です。昔、あの近くのもっと辺鄙な場所で学生してたことがあったので、名前に使ってみました。
◆
簡単な使い方。
まずは一番簡単なスタティック・メソードで呼ぶ方法。
// use as static method.
$ww = _ww::each(
array( 1,2,3 ),
function($n) { echo $n . "\n"; }
);
実際のeachの中身は、
_wwmというクラス内で定義されています。
class _wwm
{
static public function each( $collection, $iterator ) {
if( !empty( $collection ) )
foreach($collection as $k=>$v) {
call_user_func($iterator, $v, $k, $collection);
}
}
}
$collectionが配列で、foreach文を使って各要素について
$iterator関数を呼んでいます。
これだと、普通なPHPといった感じです。
次はメソードチェーンをしてみます。
◆
チェーンで次々と処理する
eachとかをつなぎ合わせてチェーンのように使えます。
// use as a chain of methods.
_ww::chain( array( 1,2,3 ) )
->each( function($n) { echo "in chain $n" . "\n"; } )
->get( $mid )
->map( function($n) { return $n * 2; } )
->value( $result );
var_dump( $mid );
var_dump( $result );
ポイントは、
- chainメソードで始めること。
- valueメソードでチェーンを終了して、結果を受け取ること。
- チェーンの途中で値を使いたい場合はgetメソードが使うこと。
といった所でしょうか。
◆
チェーンの仕方を理解する
一体どうなっているか、
_wwクラスの中を少し見てみます。
class _ww
{
private $_wrapped = NULL; // holds the data
public function __construct( $collection ) {
$this->_wrapped = (array) $collection;
}
public static function chain( $collection ) {
return new _ww( $collection );
}
}
chainメソード内で、自分を
newしてます。いや、自分自身のインスタンスを作成してます。入力された配列はオブジェクト内の
$_wrappedという変数に保存されます。
で、作ったオブジェクトをすぐにreturnしてます。
これでチェーンが始まるというわけです。
◆
メソードの呼び出し方は?
次は
eachを呼ぶのですが、_ww自体にはeachなどのメソードは実装されてません。その代わりに「magicメソード」と呼ばれる
__callメソードが呼ばれます。
class _ww
{
public function value( &$result=NULL ) {
$result = $this->_wrapped;
return $this->_wrapped;
}
public function __call( $method, $args ) {
$arg = array_merge( array( $this->_wrapped ), $input );
$return = call_user_func_array( "_wwm::$method", $arg );
if( !is_null( $return ) ) {
$this->_wrapped = $return;
}
return $this;
}
}
この中で、最初に
$_wrappedに保存しておいた配列を呼び出して、残りの引数と一緒にして、実際のeachメソードを呼び出しています(
call_user_func_array( "_wwm::$method", $arg )の部分)。
ここらへんがShinさんのarryクラスと実装が違うところです。あちらはeachとかが最初からチェーン内で使われることを想定して書かれてます。一方、元ネタのUnderscore.phpは書くメソードがチェーン内にいるかどうかを判定して、引数と返値を制御しています。ちなみに、_wwはスタティックはチェーン外、インスタンスの場合はチェーン内と割り切って、__callマジックメソードで引数などを作り直しています。
メソードの最後に
return $thisをして次のチェーンへつなぎます。
この後の
mapメソードも、同じように処理されます。ただ
mapだと返り値があるので、
$_wrappedの値が変わることになります。
こうして次から次にチェーンにしたがって、
$_wrappedに入っている配列に対して処理を行ってゆくことが出来ます。
◆
チェーン部分の出力です
in chain 1 // each内で出力
in chain 2
in chain 3
array(3) { // $midのvar_dump
[0]=> int(1)
[1]=> int(2)
[2]=> int(3)
}
array(3) { // $resultのvar_dump
[0]=> int(2)
[1]=> int(4)
[2]=> int(6)
}
◆
最後に
_wwのソースコードはgithubでホスティングしています。
ライセンスはMITライセンスです。
コードをちゃんと見ると、テストコードが入り込んでる、クラスが二つに分かれた、とか問題はたくさんあるのですが、ひとまず動いたので今回はここで完了としました。
よく「車輪の再発明」とか言われますが、こういう小さなクラスを書くのはとても楽しいです。実際に使うとか考えると微妙ですが、細かいことは気にしないで作ってみると楽しいと思います。