メタプログラミングで PHP をもっとオブジェクト指向に
PHP はブジェクト指向言語です。ですが、Ruby や JavaScript のような、より純度の高いオブジェクト指向言語とは違って、以下のようなことはできません。
puts ['foo', 'bar', 'baz'].join(',').upcase
# => FOO,BAR,BAZ
print(['foo', 'bar', 'baz'].join(',').toUpperCase());
// => FOO,BAR,BAZ
これは、Ruby や JavaScript において、配列や文字列がオブジェクトとして扱われ、メソッドを持つことができるからこそできる書き方です。
同じようなことを PHP でやろうとすると、以下のようなおぞましいことになってしまいます。
echo strtoupper(join(array('foo', 'bar', 'baz'), ','));
// => FOO,BAR,BAZ
Ruby や JavaScript のような、メソッドチェインを使った書き方と、PHP のような関数でラップしていく書き方で決定的に違うのは、やはり可読性でしょう。プログラムに書いた順番通りに処理されるので、ほとんどの人にとって、メソッドチェインの方が可読的であると考えられます。
そこで、PHP でも組み込み関数でメソッドチェインができるよう、以下のようなクラスを用意します。
class MyObject
{
private $_selfMember;
public function __construct($param)
{
$this->_selfMember = $param;
}
public function __call($name, $args)
{
$new_args = array_merge(
array($this->_getSelfMember()),
$args
);
return new self(call_user_func_array($name, $new_args));
}
public function _getSelfMember()
{
return $this->_selfMember;
}
public function __toString()
{
return print_r($this->_selfMember, true);
}
}
このクラスを使うと、以下のようなコードが書けるようになります。
$obj = new MyObject(array('foo', 'bar', 'baz'));
echo $obj->join(',')->strtoupper();
// => FOO,BAR,BAZ
PHP の文法の制約上、ここまでが限界ですが、何とかそれらしくなりました。join も strtoupper も PHP の組み込み関数ですが、あたかもオブジェクトのメソッドのように呼び出すことができています。
この実装を可能にしているのが、PHP のマジックメソッドと呼ばれる、特殊なメソッドです。
__call メソッドは、メソッドが存在しない時に実行されます。引数として、実行しようとしたメソッドの名前と、その引数を受けるので、call_user_func_array に渡して、関数を実行しています。Ruby でいうと method_missing メソッドに相当します。
そして、上記のコードで、__call メソッドの返り値は、MyObject を new した新たなオブジェクトになっているので、このままでは、これを echo することはできません。
ここで、__toString を使うと、オブジェクトを文字列として出力するときの形式を定義できます。ここでは print_r を使っています。Ruby でいうと、to_s メソッドに相当します。 (ここでの動作は inspect メソッド的ですが)
ただし、この MyObject クラスは、あくまでも簡易的なものなので、存在しない関数を実行しようとしたときのエラー処理等をしていません。また、内部で実行する関数への、引数の渡し方によっては、うまく動作しません。例えば、explode や preg_replace など。
いろいろと問題点があって、MyObject 自信は使い物にはなりませんが、マジックメソッドによるメタプログラミングは、PHP によるプログラムの書き方の可能性を、大きく広げていると言えるでしょう。
だからと言って、チーム開発でこのようなコードを書くと、自分以外のメンバーに混乱を招くことになるので、安易に使うのはオススメできませんが・・・。