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)