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)
PHP5.3がいつのまにかリリースされていたのでクロージャを使ってみた
PHP5.3リリースされてたんですね。
気づきませんでした・・・。
RCが出た時期にちょうど仕事が多忙で試せて無かったので、落ち着いた今、喜び勇んででstableをインストールしました。
で、さっそくクロージャを普通に書いてみました。
<?php $hoge = function () { echo 'hogehoge'; }; $hoge(); ?>
当たり前ですが、正常に動いた!
で、個人的にできるのかすごい気になってた事…
インスタンスに動的にメソッドの追加はできるのか?
<?php class Hoge { } $hoge = new Hoge(); $hoge->method = function () { echo 'method'; }; $hoge->method(); ?>
Fatal error: Call to undefined method Hoge::method() in ...
やっぱり動かないのかー!
これが動いたらフレームワークで__call()を実装しなくてもよくなるので実効速度的に改善できるかなって思ってただけに少し残念です。
PHPで無理やりfriendを実装してみる
前の日記からえらく時間が開いちゃいました☆
今回は、C++などにあるfriendを無理やり実装してみます。
friendについては、ぐぐって下さい。
friend class - Google 検索
<?php /** * Friend * Friendを無理やり実装してみた抽象クラス */ abstract class Friend { /** * @var $friends array アクセスを許可するクラス名を配列で定義します */ protected $friends; // ------------------------------------------------------------------------ /** * isFriend * アクセス元がフレンドか判断する * * @access protected * @return bool */ protected function isFriend () { $trace = debug_backtrace(); return in_array( $trace[count( $trace ) - 1]['class'], $this->friends ); } } ?>
たったこれだけです。
使い方は、↓
<?php class TestFriend { public function callFriendMethod ( Test $obj ) { $obj->forFriend(); } } // friendとしてアクセスを許可するクラスがFriend抽象クラスを継承する。 class Test extends Friend { protected $friends=array( 'TestFriend' ); public function forFriend () { if( $this->isFriend() === false ) { echo 'アクセスが拒否されました!'; return; } echo 'アクセスに成功しました!'; } } $test = new Test(); $test_friend = new TestFriend(); $test->forFriend(); $test_friend->callFriendMethod( $test ); ?>
ご覧の通り、メソッド以外に適用できません orz...
プロパティに使いたい場合、__getとか__setにisFriend()を使うしか無いでしょう。
あと、重要な速度ですが、50万回ループで
isFriend()あり | 16.496188879sec |
isFriend()なし | 2.96278500557sec |
これはw
使えません!