PHPで簡易メタプログラミング。mixinとかやっちゃう

前の記事書いてから1年以上経過してました。
ちょっくらIT業界から離脱してたりしましたw


ここ1ヶ月ぐらい、久々にIT業界に戻ってきたので、プログラミングeverydayです。
久々にプログラミングするの楽しいぃ!!

本日から日記を頑張って書いてみようと思います!


まぁそんなこんなで、PHPメタプログラミングしたくなったので、手っ取り早くmixinを実装してみました。


ソースはこんな感じです↓

<?php
/**
 * Mixinクラス
 *
 * PHPのマジックメソッドを多様し、PHPで無理やりmixinを実装するクラス
 * staticでの静的呼び出しを行うので、PHP5.3以降のみに対応しています。
 *
 * @access  public
 * @create  2010/09/15
 **/
abstract class Mixin {

    protected $mixins;

    protected $__instances;

    protected $__methods;

    protected static $__static_methods;

    protected $__properteis;

    /**
     * コンストラクタ
     *
     * @access    public
     * @return    void
     */
    public function __construct () {

        if( is_array( $this->mixins ) === false ) return;

        $loaded_classes = get_declared_classes();

        foreach( $this->mixins as $mixin ) {

            // クラスロード済み?
            if( in_array( $mixin, $loaded_classes ) === false ) continue;

            // メソッド取得
            $methods = get_class_methods( $mixin );

            if( count( $methods ) === 0 ) continue;

            foreach( $methods as $method ) {

                // マジックメソッドはスキップする
                if( strpos( $method, '__' ) === 0 ) continue;

                $reflector = new ReflectionMethod( $mixin, $method );

                $a = new ReflectionClass( $mixin );

                // static?
                if( $reflector->isStatic() === true ) {
                    self::$__static_methods[ $method ] = $this->__getInstance( $mixin );
                } else {
                    $this->__methods[ $method ] = $this->__getInstance( $mixin );
                }
            }

            // プロパティ取得
            $properties = get_class_vars( $mixin );

            if( count( $properties ) === 0 ) continue;

            foreach( $properties as $key => $value ) {
                $this->__properteis[ $key ] = $value;
            }
        }
    }


    /**
     * mixinの元になったインスタンスを返す
     * 無ければインスタンス化する
     *
     * @access    public
     * @param     String    $class_name    クラス名
     * @return    Object
     */
    protected function __getInstance ( $class_name ) {

        if( isset( $this->__instances[$class_name] ) === false ) {
            if( in_array( $class_name, get_declared_classes() ) === false ) return null;
            $this->__instances[$class_name] = new $class_name();
        }

        return $this->__instances[$class_name];
    }


    /**
     * __call
     *
     * @access    public
     * @param     String    $name    メソッド名
     * @param     mixed     $args    変数
     * @return    mixed
     */
    public function __call ( $name, $args ) {

        if( isset( $this->__methods[ $name ] ) === false ) {
            self::__undifinedMethodError();
            return;
        }

        if( get_class( $this->__methods[ $name ] ) === 'Closure' ) {
            return call_user_func_array( $this->__methods[ $name ], $args );
        } else {
            return call_user_func_array( array( $this->__methods[ $name ], $name ), $args );
        }
    }


    /**
     * __callStatic
     *
     * @access    public
     * @param     String    $name    メソッド名
     * @param     mixed     $args    変数
     * @return    mixed
     */
    public static function __callStatic ( $name, $args ) {

        if( isset( self::$__static_methods[ $name ] ) === false ) {
            self::__undifinedMethodError();
            return;
        }

        $class_name = get_class( self::$__static_methods[ $name ] );

        return forward_static_call_array( array( $class_name, $name ), $args );
    }


    /**
     * __get
     *
     * @access    public
     * @param     String    $name    プロパティ名
     * @return    mixed
     */
    public function __get ( $name ) {

        if( isset( $this->__properteis[ $name ] ) === false ) return null;

        return $this->__properteis[ $name ];
    }


    /**
     * __set
     *
     * @access    public
     * @param     String    $name    プロパティ名
     * @param	  mixed		$arg	 セットする値
     * @return    void
     */
    public function __set ( $name, $arg ) {

        if( gettype( $arg ) === 'object' && get_class( $arg ) === 'Closure' ) {
            $this->__methods[ $name ] = $arg;
        } else {
            $this->__properteis[ $name ] = $arg;
        }
    }


    /**
     * __undifinedMethodError
     *
     * @access    public
     * @return    void
     */
    public static function __undifinedMethodError () {

        set_error_handler( function ( $error_no, $error_str ) {
            if( E_USER_ERROR != $error_no ) return;
            $trace = debug_backtrace();

            $trace = array_pop( $trace );

            echo "\n".'<br><b>Notice</b>:  Call to undefinded '.( $trace['type'] === '::' ? 'static ' : '' ).'function: '.$trace['class'].'::'.$trace['function'].'() in <b>'.$trace['file'].'</b> on line <b>'.$trace['line'].'</b><br /> ';
            exit(1);
        } );

        trigger_error( '', E_USER_ERROR );

        restore_error_handler();
    }
}
?>


ちょっとエラーハンドラをオーバーライドしてたり、やる気を振り絞って書いてみました。
まぁ少しエラー処理で甘い部分もあると思いますが、使えなくはないと思います。


実装は簡単で、$mixinプロパティをコンストラクタで確認し、クラスが定義されていたら、インスタンス化して行って、メソッドやらプロパティを所定のプロパティに格納して行きます。



使い方はこんな感じになります↓

<?php
require( 'mixin.class.php' );

class Human {

    public function punch () {
        return 'punch!!';
    }

    public static function kick () {
        return 'static kick!!';
    }
}

class Bird {

    public function fly () {
        return 'I can fly!!';
    }
}

class Chimera extends Mixin {

    protected $mixins = array ( 'Human', 'Bird' );
}


$chimera = new Chimera();

// Humanクラスのメソッドをコール
echo $chimera->punch();
echo "<br />\n";

// Humanクラスメソッドの静的コール
echo Chimera::kick();
echo "<br />\n";

// Birdクラスのメソッドをコール
echo $chimera->fly();
echo "<br />\n";

// 動的メソッドの追加
$chimera->hadoken = function () { return 'hadoken!'; };
echo $chimera->hadoken();

// 変数付きメソッドの追加
$chimera->tatsumaki = function ( $count ) { return 'tatsumaki'.$count; };
echo $chimera->tatsumaki( 20 );

?>


人間クラスと鳥クラスをキメラクラスにmixinします。
ここまでは、実装方法は違えどネットには結構転がっていたので、ちょっとスパイス的に動的にメソッドを追加する機構と__callStaticを使って静的呼び出しにも対応してみました。


PHP5.3専用ですが、動的メソッド追加部分はcreate_function()とかで代用したりすれば5.2以前でも動くと思います。(試していませんw)