ご挨拶
シュバルツシルトドラゴンを求めて黒輪縛鎖を買い続けているけれど、まだ一枚も手に入っていないカードファイター石田です。おかげさまでネビュラロードドラゴン中心のデッキは作れましたが、やはりそこは連携ライドのロマンを追いたいですよね。現状のリンクジョーカーはよく言われるほど凶悪なクランではないと思うのですが対戦者にどうも嫌な顔されるのが辛い今日この頃です。
リフレクションって何?
PHPにはリフレクションという機能があります。リフレクションが何かを分かっている人にとっては「おいおい、PHPにリフレクションなんか必要ねーだろ」みたいな声が聞こえてきそうです。
「だって……
1 2 3 4 5 6 7 8 9 |
class Hello { public function world() { return 'world'; } } $class = 'Hello'; $obj = new $class(); $method = 'world'; print get_class($obj) . ' ' . $obj->$method(); |
とか出来ちゃうじゃん要らないじゃん!」
いや、ごもっともなんですが実際に必要な時も有るんだよということは読み進めてもらえるとなんとなく伝わるかと思います。さて、本節はリフレクションが分からない人向けの説明です。が、うだうだ書こうと思ったところ良い説明がWebで見付かったので、それを引用しちゃいます。
リフレクションは、プログラムの中でそのプログラムに含まれる型や変数/メソッドの情報を参照/操作できるようにする仕組みです。Javaではjava.lang.reflectパッケージに、.NET FrameworkではSystem.Reflection名前空間に、それぞれリフレクションを利用するためのクラスが用意されています。Ruby、Python、PHP(PHP:Hypertext Preprocessor)などのスクリプト言語も、リフレクションの機能を備えています。
引用元: 日経BPのコラム
ということですね。しかし、PHPは文字列変数から名前の一致するクラスをそのまま newしたり、文字列の変数からメソッド名を指定して実行出来ちゃったりするので、そんなに必要無いんじゃないかって話ではあります。実際に知らなくてもあまり困ることも無いんじゃないかと思います。しかし、単体テストをし易いクラス設計をするための基盤を作ったり、しょーもないコードを減らしたい時には意外と重要だったりもするのです。実際のリフレクションAPIの詳細についてはマニュアルを当ってもらった方が分かり易いと思いますので、本記事では概略と実際にどうやって使うのかという話をしていこうと思います
リフレクションAPIの構成要素
実際には、ここで挙げる以外にも有るのですが僕の独断と偏見で主に使うと思い込んでいるモノだけ紹介しようと思います。もっと詳しく知りたい方は先述のマニュアルを読んでください。
ReflectionClass |
クラスの構造を取り出したりインスタンス化したりします |
---|---|
ReflectionMethod |
メソッドの構造を取り出したりメソッドを実行したり、メソッドからパラメータの定義を取りだしたりします。現状ではタイプヒンティングは取り出せません |
ReflectionParameter |
パラメータの定義です。現状ではタイプヒンティングは取り出せません |
ReflectionProperty |
プロパティの定義です。プロパティにアクセスしたりします |
では、実際これらをどのように使うのか? ということについて解説していこうと思います。次のコードを見てみてください。これはよく言うところのDI(依存性を注入しながらnewしてくれるやつ)みたいなモノです。実用したりするのは循環構造で依存している場合を考慮したりしないとイケないので、そういうのは頑張ってください。ソースコード中にコメントの形で解説を挟むので読んで理解してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
/// 依存性注入機能付き汎用ファクトリです /// メソッドやコンストラクタのDocコメントに "@inject クラス名"の指定が有れば、 /// そのインスタンスを代入したインスタンスを返します。 class DI { // // オブジェクトを依存性注入しながらnewします // public static function createObject($className) { if (!$className) return null; // 対象のクラスの構造を取り出します $class = new \ReflectionClass($className); $result = null; $method = null; // コンストラクタを満たします if ($class->hasMethod("__construct")) { $method = $class->getMethod('__construct'); $params = self::createDependency($method); $params = self::fillParameterWithNull($method, $params); $result = $class->newInstanceArgs($params); } // 定義が無ければ適当にnewします else { $result = $class->newInstance(); } // メソッドの定義を取り出して、依存性を注入していきます。 foreach ($class->getMethods() as $method) { $params = self::createDependency($method); if (!$params) continue; $params = self::fillParameterWithNull($method, $params); // パラメータを設定しながら対象のメソッドを実行です $method->invokeArgs($result, $params); } return $result; } // // メソッドの定義から依存インスタンスを生成して配列に詰めて返します // public static function createDependency($method) { $params = array(); $class = $method->getDeclaringClass(); // Docコメントを取り出します。アノテーション代りに利用しています。 $docs = $method->getDocComment(); if ($docs) { $matches = array(); // アノテーションを正規表現で適当に取り出しています。 // 実用するのであれば真面目に解析してください。 if ( preg_match_all('#@inject\s+(\S+)#', $docs, $matches) && count($matches)) { // アノテーションから取り出した依存クラス名から インスタンスを生成して配列に詰め込みます。 foreach ($matches[1] as $clazz) { // 実用するなら同じクラスのインスタンスだけではなくて、 // 循環依存もチェックしてください。 if($class->getNameSpaceName() == $clazz) throw new Exception('同じクラスのインスタンスの注入は出来ません'); // 依存インスタンスも再帰的に生成していきます $params[] = self::createObject($clazz); } } } return $params; } // // パラメータの定義から省略可能引数にnullを詰めてパラメータの要求個数に合わせます // public static function fillParameterWithNull($method, $params) { $parameterDefinition = $method->getParameters(); for ($i = count($params); $i < count($parameterDefinition); $i++) { if ($parameterDefinition[$i]->allowsNull()) { $params[$i] = null; } else { throw new Exception('注入指定の他にNULL許容不可のパラメータが存在します'); } } return $params; } } class Hello { private $prop = null; private $prop2 = null; /** * @inject World1 * @inject World2 */ public function __construct($arg1, $arg2) { $this->prop = $arg1; $this->prop2 = $arg2; } } class World1 {} class World2 {} var_dump ( DI::createObject('Hello')); |
なんとなく、どんなことが出来るのか? というのが掴めたのではないかと思います。実際には業務ロジックで使えるシーンは殆ど無いのではないかと思います。しかし、プロジェクトのスタートアップ時に、フルスタックなフレームワークを使う程、大きくないし…… とか、目的特化の基盤を作りたいし…… みたいなシーンでは活躍するのではないでしょうか? 依存性注入の他には、こんな目的にも使えそうです
-
・異るクラスのインスタンス間でのディープコピー自動化
-
・POPO(Plain Old PHP Object)のままでORマッピング
-
・POPO(Plain Old PHP Object)を、そのままMVCのコントローラとして利用する
などなど。PHPの場合、勿論リフレクションじゃなくても出来ることも有ると思いますが、夢と利用方法は広がっていくんじゃないかと思います。しょーもない代入をひたすら繰り返すだけのコードを撒き散らす必要が生まれそうになったらリフレクションのことを思い出してみてください。
終わりに
アライドアーキテクツではリンクジョーカー相手でも爽やかにファイトしてくれるカードファイターと、リフレクションが大好きだけど”DI”という言葉を使うのはこそばゆい派のエンジニアを募集しています。
なるべく他のメンバーは書かないであろう流行りとは程遠いところや、 枯れたキーワードについて書いていこうと思います。 Perl / Java / PHP 及び周辺技術に興味があります。