jQuery で Ajax リクエストを行う場合, パラメータはハッシュ (JavaScript の Object 型) で指定して実行します.

この例だとまだシンプルに見えますが, success コールバックなどが肥大化してくると, 見通しが悪くなってきます.

そこで以下のようなコードを用意します.
jQuery.ajax() のラッパーとなる FluentAjax オブジェクトと, それを呼び出すヘルパーとして fajax() 関数を定義しました.

メソッドの定義にはメタプログラミングを使用しており, 実質上のプログラムは約 30 行程度です.

これを利用すると, 同じ Ajax リクエストを以下のように行うことができるようになります.

メソッドチェインでリクエストオプションを組み立てていき, 最後に execute() メソッドでリクエストを実行しています.
いわゆる Fluent Interface (流れるようなインターフェイス) になっていると思います.

メソッドチェインを使った方が可読性が高い… かどうかは好みの問題かもしれませんが, ひとつのサンプルとして.
これをもう少し抽象化すれば, 1 つのハッシュを引数にした複雑な関数は, 何でも Fluent Interface にできそうですね.

See also

, , ,

昨日投稿したメタプログラミングで PHP をもっとオブジェクト指向にだと、以下のようにしか書けませんでした。

$obj = new MyObject(array('foo', 'bar', 'baz'));
echo $obj->join(',')->strtoupper();
// => FOO,BAR,BAZ

どういうことかというと、new の直後でメソッドを呼び出すことができないようで、上記のように 2 行で書かざるを得なくなっていました。

そこで、MyObject クラスを以下のように書き換えました。

class MyObject
{
    private $_selfMember;

    public function __construct($selfMember)
    {
        $this->_selfMember = $selfMember;
    }

    public static function init($selfMember)
    {
        return new self($selfMember);
    }

    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);
    }
}

変更点は、init というスタティックメソッドを追加しただけです。

この変更により、以下のように書けるようになりました。

echo MyObject::init(array('foo', 'bar', 'baz'))->join(',')->strtoupper();
// => FOO,BAR,BAZ

楽しいような、危なっかしいような感じですが、言いたいこととしては、マジックメソッドを使うといろいろおもしろいことができそうですよ!ということにさせてください。こういう、いかにも役に立たなそう (というかむしろ害悪になりそう) なコードでも、遊びでいろいろ試しているうちに、実用に耐えうるアイディアに化けるかもしれません・・・。

, ,

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 によるプログラムの書き方の可能性を、大きく広げていると言えるでしょう。

だからと言って、チーム開発でこのようなコードを書くと、自分以外のメンバーに混乱を招くことになるので、安易に使うのはオススメできませんが・・・。

, ,