モンモンブログ

技術的な話など

php5.3 以前で mixin 的なものを実装

php5.4 以降であれば Trait を使って mixin 的なことは実現できますが(※)、 php5.3 以前で mixin したい場合はどうするか。

いろいろ考えましたが、一番シンプルで使いやすいかなーという方法を紹介します。

※mixin と trait の違いとかよう分かってへんけどこまけぇこたぁいいんだよ!(AA略

ここが素敵

  • インスタンスメソッドも静的メソッドも mixin 出来ます(ただし静的メソッドの mixin は php5.3.0 以降でのみ可能)。
  • もちろん mixin したメソッドに引数を渡すことも出来ます。
  • mixin したインスタンスメソッドの中から、対象クラスの public なメンバ変数にアクセス出来ます。
  • 複数の mixin クラスを同時に mixin 出来ます。

まずはコード

サンプルコードです。

Trait が導入される php5.4 より前の php で mixin 的なことを実装してみた。

これを実行するとこう。

$ php mixin.php
// $foo->mixin_instance_method
これは MixinClass のインスタンスメソッドです yahoooooo! 2013-10-11
Foo の public メンバ変数にアクセスすることも出来ます。 999

// $foo->mixin2_instance_method
これは MixinClass2 のインスタンスメソッドです

// $foo->instance_method
これは Foo 自身のインスタンスメソッドです

// $foo->parent_instance_method
これは親クラス Bar のインスタンスメソッドです

// Foo::mixin_static_method
これは MixinClass の静的メソッドです yahoooooo! 2013-10-11

// Foo::mixin2_static_method
これは MixinClass2 の静的メソッドです

// Foo::static_method
これは Foo 自身の静的メソッドです

// Foo::parent_static_method
これは親クラス Bar の静的メソッドです

     *      *
  *     +
     n ∧_∧ n  IINE!
 + (ヨ(* ´∀`)E)
      Y     Y    *

いいねー。

解説

では解説です。インスタンスメソッド版 mixin と静的メソッド版 mixin の2つに分けて説明します。

1. インスタンスメソッド版 mixin

さっきのコードから、インスタンスメソッドの mixin に関わるとこだけ切り出すとこう。

インスタンスメソッドの mixin

1-1. mixin クラスを作成

まずは mixin クラスを作りましょう。

ほぼ、普通にクラス定義するだけですけど一点注意。 インスタンスメソッドの第1引数は $that とか $self とかの名前にして下さい。 ここに mixin 対象クラスのインスタンスが渡されてきます。これを通じて mixin 対象クラスの public なメンバ変数にアクセス出来るようになります。

// このように呼ぶメソッドであれば、
$foo->message("hello.");

// 定義はこうではなく、
public function message($message) { /* (´・ω・`) */ }

// こうです。
public function message($that, $message) { /* (・∀・)イイネ!! */ }

// 引数なしで呼ぶメソッドであっても、
$foo->bar();

// 定義はこうではなく、
public function bar() { /* (´・ω・`) */ }

// こうです。
public function bar($that) { /* (・∀・)イイネ!! */ }

1-2. mixin 対象クラスを修正

mixin 対象クラスをいじって __construct と __call をオーバーライドします。

__construct で MixinClass のインスタンスを生成してメンバ変数に持っておきます。

    public function __construct()
    {
        $this->mixin1 = new MixinClass();
    }

__call では、渡されてきたメソッド名 $method_name が MixinClass のインスタンスメソッドとして実行可能か調べて、そうあれば実行。 実行する際、第1引数に $this を渡します。これがさっき mixin クラス側でメソッドの第1引数とした $that (or $self) です。

    public function __call($method_name, $args)
    {
        // Foo 自身を表す $this を引数リストの先頭に追加します
        array_unshift($args, $this);
        // $this->mixin1->$method_name() が実行可能か調べる
        if (is_callable(array(&$this->mixin1, $method_name))) {
            // MixinClass#$method_name を実行
            return call_user_func_array(array(&$this->mixin1, $method_name), $args);
        } else {
            // そんなインスタンスメソッドはどこにも見つかりませんでした。。。
            throw new BadMethodCallException();
        }
    }

2. 静的メソッド版 mixin

静的メソッドの mixin に関わるとこだけ切り出すとこう。

静的メソッドの mixin

2-1. mixin クラスを作成

静的メソッドを持つクラスをふつーに定義するだけです。

2-2. mixin 対象クラスを修正

mixin 対象クラスでは __callStatic をオーバーライドします。MixinClass::$method_name が実行可能かどうか調べて、あれば実行。

    public static function __callStatic($method_name, $args)
    {
        if (is_callable("MixinClass::$method_name")) {
            // MixinClass::$method_name を実行
            return call_user_func_array("MixinClass::$method_name", $args);
        } else {
            // そんな静的メソッドはどこにも見つかりませんでした。。。
            throw new BadMethodCallException();
        }
    }

おしまい。

お解り頂けたかしら_(:3」∠)_