はじめに
この記事で紹介する機能は、PHP4からPHP5にまだアップグレードしていない人にとっては、大いに心引かれる機能だと思います。それは、PHP5で新たにサポートされたリフレクションAPIです。リフレクションAPIを使用することで、変数、インターフェイス、関数、メソッド、パラメータ、クラスなどの詳細情報を入手できます。リフレクションAPIは機能が豊富で、多数のクラスやメソッドを利用してリフレクションを実現できます。
この記事ではまず、リフレクションAPIの概念について理解していただくために、リフレクションAPIのクラスとメソッドについて、短い例を交えて簡単に説明します。その後で、PHPで作成した実際的なサンプルアプリケーションを2つ紹介します。1つは、呼び出すメソッドを実行時に判別するアプリケーションで、もう1つは、PHPクラスの情報を示す整形されたHTMLドキュメントを自動生成するアプリケーションです。
リフレクションAPIのクラスとメソッド
リフレクションAPIの機能の概略を示すために、リフレクションAPIのクラスとメソッド、およびそれらの関係を表す図を紹介します(図1)。これを見るとわかるように、リフレクションAPIのほとんどのクラスは、Reflectorインターフェイスを実装しています(Reflection、ReflectionException、およびReflectionFunctionAbstractは除く)。エクスポート可能なすべてのリフレクションクラスがReflectorインターフェイスを実装しています。
ReflectionClass
ReflectionClassクラスは、リフレクションAPIの主役となるクラスです。このクラスを使用することで、他のクラス群へのリフレクションを適用し、その時点での全クラスコンポーネントについての情報を抽出します。ReflectionClassのメソッドにはよく似たものが多いので、ここでは、いくつかのメソッドについてのみ使用例を紹介し、残りはメソッド名と説明を示すにとどめます。
public void construct(string name) ― ReflectionClassのコンストラクタです。
public string getName() ― 調査したクラス名を返します。
// EXAMPLE
$class = new ReflectionClass('TestClass');
echo "The class name: ".$class->getName();
public ReflectionMethod getConstructor() ― 解析されたクラスコンストラクタを表すReflectionMethodオブジェクトを返します。
// EXAMPLE
$class = new ReflectionClass('TestClass');
$constructor = $class->getConstructor();
echo $constructor;
public ReflectionMethod getMethod(string name) ― nameパラメータで指定したメソッドを表すReflectionMethodオブジェクトを返します。
// EXAMPLE
$class = new ReflectionClass('TestClass');
$method = $class->getMethod('testMethod_1');
echo $method;
public ReflectionMethod[] getMethods() ― 解析されたすべてのクラスメソッドを表すRelectionMethodオブジェクトの配列を返します。
// EXAMPLE
$class = new ReflectionClass('TestClass');
$methods = $class->getMethods();
foreach($methods as $in)
{ echo $in; }
このクラスには、上記の挙げたメソッドの他にも、以下のような同様のメソッドがあります。使い方も同様です。つまり、調査するクラスの名前をパラメータで渡してReflectionClassのインスタンスを作成し、そのインスタンスを使用して以下のメソッドを呼び出します。
- public ReflectionProperty getProperty(string name)
- public ReflectionProperty[] getProperties()
- public mixed getConstant(string name)
- public array getConstants()
- public ReflectionClass[] getInterfaces()
以上のメソッドで取得したオブジェクトを使用すると、クラスの内部をさらに掘り下げた情報にアクセスできます。これについて、次のセクションで説明します。
ReflectionMethodクラス、ReflectionPropertyクラス、ReflectionFunctionクラス
ReflectionMethodクラスでは、メソッドに対してリフレクションを適用して、解析されたメソッドについての個別情報を取得できます。ここでも、いくつかの例と類似するメソッドの名前を示します。
public void __construct(mixed class, string name) ― ReflectionMethodのコンストラクタです。
public mixed invoke(stdclass object [, mixed args [, ...]]) ― 解析されたメソッドを呼び出すときに使います。
// EXAMPLES
//call a non-static method with no arguments
$testClass = new TestClass();
$method = new ReflectionMethod('TestClass', 'testMethod_1');
echo $method->invoke($testClass);
//call a non-static method with two arguments
$testClass = new TestClass();
$method = new ReflectionMethod('TestClass', 'testMethod_2');
echo $method->invoke($testClass, 'testValue_1', 'testValue_2');
//call a static method with no arguments
$method = new ReflectionMethod('TestClass', 'testMethod_3');
echo $method->invoke(NULL);
//call a static method with two arguments
$method = new ReflectionMethod('TestClass', 'testMethod_4');
echo $method->invoke(NULL,'testValue_1','testValue_2');
他にも、次のようなメソッドがあります。
ReflectionMethodクラスでメソッドにリフレクションを適用するのと同じように、ReflectionPropertyクラスでは、プロパティにリフレクションを適用できます。このクラスのメソッドはReflectionMethodクラスのメソッドとほぼ同じなので、最小限の例のみを示すことにします。ReflectionPropertyオブジェクトの構築には次のメソッドを使用します。
public void __construct(mixed class, string name)
プロパティ値の取得には次のメソッドを使用します。
public mixed getValue(stdclass object):
getValueメソッドのパラメータにはクラスインスタンスを渡します。すると、そのインスタンスの対象プロパティの値が返ります。
$testClass= new TestClass();
$property = new ReflectionProperty('TestClass', 'testProperty_1');
echo $property->getValue($testClass);
値を設定するにはsetValueメソッドを使用し、クラスインスタンスと新しいプロパティ値の両方を渡します。使用例は次のとおりです。
$testClass= new TestClass();
$property = new ReflectionProperty('TestClass', 'testProperty_1');
echo 'Before : '.$property->getValue($testClass).'<br />';
$property->setValue($testClass,"new_testProperty_1");
echo 'After : '.$property->getValue($testClass);
ReflectionFunctionは、ReflectionMethodクラスやReflectionPropertyクラスと同様に、関数にリフレクションを適用するためのクラスです。関数を呼び出すにはinvokeメソッドを呼び出し、必要に応じてパラメータを渡します。構文は次のとおりです。
public mixed invoke([mixed args [, ...]])
簡単な例を次に示します。
$function = new ReflectionFunction('testFunction_1');
echo $function->invoke();
最後に紹介するのはReflectionParameterクラスです。関数やメソッドのパラメータにリフレクションを適用します。ReflectionParameterのインスタンスを作成するときには、関数名とパラメータの位置を渡します(位置は文字列で渡します。1つ目のパラメータはarg1、2つ目のパラメータはarg2、という形です)。
$parameter = new ReflectionParameter('testFunction_2','arg1');
echo $parameter->getName();
パラメータのデフォルト値を取得するには、getDefaultValueメソッドを使用します。また、isPassedByReferenceメソッドを使用すると、関数の特定のパラメータが参照渡しか値渡しかを調べることができます。パラメータが参照渡しの場合にはブール値のtrueが返ります。
リフレクションAPIには、ここで紹介した以外にも、ReflectionException、ReflectionObject、ReflectionExtension、ReflectionFunctionAbstractといったクラスがあり、それぞれのクラス名が示す項目についての情報を得ることができます。全体を網羅したドキュメントとチュートリアルについては、PHP5の公式マニュアルを参照してください。
例1:リフレクションを利用した簡単なアプリケーション
本稿のサンプルアプリケーションのテストには、リスト1に示すTestClassクラスを使用しました。このクラスのコードは本稿の冒頭のリンクからダウンロードできます。
リスト1 TestClassクラス
<?php
/**
* Test function (no arguments)
*/
function testFunction_1(){
return 'You just called the function testFunction_1...';
}
/**
* Test function (two arguments)
*/
function testFunction_2($arg1,$arg2){
return 'You just called the function testFunction_2...';
}
/**
* Test interface (1)
*/
interface TestInterface_1 {
}
/**
* Test interface (2)
*/
interface TestInterface_2 {
}
/**
* Test superclass
*/
class SuperTestClass{
function __construct() {}
}
/**
* Test class for Reflection API
*/
class TestClass extends SuperTestClass
implements TestInterface_1, TestInterface_2 {
public static $testProperty_1 = "testProperty_1";
protected $testProperty_2 = "testProperty_2";
private $testProperty_3 = "testProperty_3";
const testConstant_1 = "testConstant_1";
const testConstant_2 = "testConstant_2";
const testConstant_3 = "testConstant_3";
/**
* The TestClass constructor
*/
function __construct() {
}
/**
* testMethod_1 (no arguments)
*/
function testMethod_1(){
return 'You just called the public method testMethod_1...';
}
/**
* testMethod_2 (two arguments)
*/
function testMethod_2($arg_1, $arg_2){
return "You just called the public method testMethod_2 with " +
"two arguments: arg_1 = ".$arg_1." , arg_2=".$arg_2;
}
/**
* static testMethod_3 (no arguments)
*/
static function testMethod_3(){
return "You just called the static method testMethod_3...";
}
/**
* static testMethod_4 (two arguments)
*/
static function testMethod_4($arg_1, $arg_2){
return "You just called the static method testMethod_4 with " +
"two arguments: arg_1 = ".$arg_1." , arg_2=".$arg_2;
}
}
?>
以降では、前節で示したリフレクションAPIの情報を使用して開発した、簡単な通貨換算アプリケーションの例を紹介します。処理の出発点は「index.htm」というHTMLページです(図2)。このページ自体には特別変わった点はありません。
HTMLは次のとおりです。
<html>
<head>
<h3>Currency converter</h3>
</head>
<body>
<form method="get"
action="currencyconverter.php">
<b>Convert this amount:<br /><br /></b>
<input type="text" value="1" name="amount"> <br /><br />
<hr>
<b> From this currency:</b><b>
To this currency </b> <br /><br />
<select name="currency1" size="1">
<option value="EUR">EUR
<option value="USD">USD
<option value="GBP">GBP</select>
<select name="currency2" size="1">
<option value="EUR">EUR
<option value="USD">USD
<option value="GBP">GBP</select>
<hr>
<input type="submit" value="Exchange">
<input type="reset" value="Reset">
</form>
</body>
</html>
肝心なのは、ユーザーが値を入力し、換算元と換算先の通貨を選択してからの処理です。換算元と換算先を示すドロップダウンリストには、3種類の通貨を示す略語が表示されています。米ドルを表す「USD」、英ポンドを表す「GBP」、ユーロを表す「EUR」です。ユーザーが[Exchange]ボタンをクリックすると、フォームからデータが送信され、CurrencyConverterという名前のPHPクラスインスタンスがサーバーによって作成されます。このクラスには、起こり得る6種類の換算のそれぞれに対応した6種類のメソッドが備わっています。
<?php
class CurrencyConverter {
function __construct() {}
function EUR_USD($eur) {
$usd=1.38745*$eur;
return $eur.' EUR = '.$usd.' USD ';
}
function USD_EUR($usd) {
$eur=0.7274*$usd;
return $usd.' USD = '.$eur.' EUR ';
}
function GBP_EUR($gbp) {
$eur=1.44610*$gbp;
return $gbp.' GBP = '.$eur.' EUR ';
}
function EUR_GBP($eur) {
$gbp=0.69151*$eur;
return $eur.' EUR = '.$gbp.' GBP ';
}
function GBP_USD($gbp) {
$usd=2.00640*$gbp;
return $gbp.' GBP = '.$usd.' USD ';
}
function USD_GBP($usd) {
$gbp=0.49840*$usd;
return $usd.' USD = '.$gbp.' GBP ';
}
}
?>
換算に使用するメソッドは、ユーザーが選んだ通貨に応じて変わるので、呼び出すべき適切なメソッドを実行時に判断する必要があります。一連の条件分岐ステートメントで判断したり、call_user_funcメソッドを使用したりという手もありますが、PHP5のリフレクションを使用すれば、もっとエレガントに処理を実現できます。呼び出すメソッドを判断するのは簡単です。CurrencyConverterのメソッドの名前は、EUR_USDやUSD_EURなどの形式になっています。ユーザーが2つの通貨を選択すると、コンソールから、それらの通貨を表す略語が送信されます。PHPのコードでは、送信された2つの通貨の文字列をアンダースコアで連結すればいいのです。たとえば、ユーザーが選んだ換算元がユーロ(EUR)で、換算先がポンド(GBP)の場合、アプリケーションで呼び出すのはEUR_GBPメソッドです。アプリケーションでそのメソッドを実際に呼び出すときには、リフレクションを使用します。以上の一連の処理のコードは「currencyconverter.php」ページにあり、次のようになっています。
<?php
//include the CurrencyConverter class
include("CurrencyConverter.inc");
//Get the amount to be converted
$amount=$_GET['amount'];
//From this currency...
$currency1=$_GET['currency1'];
//To this currency...
$currency2=$_GET['currency2'];
//Obtain the correct method name
$name=$_GET['currency1'].'_'.$_GET['currency2'];
//get a ReflectionMethod for the $name method
$reflectionMethod=new ReflectionMethod(
'CurrencyConverter', $name);
//create an instance of the CurrencyConverter class
$currencyConverter=new CurrencyConverter();
//invoke the correct method
echo $reflectionMethod->invoke($currencyConverter, $amount);
?>
例2:リフレクションによるPHPドキュメントの生成
次は、リフレクションを使用してPHPクラスのドキュメントを生成する例です。この処理には、クラス内部を掘り下げて解析する必要があり、したがってリフレクションAPIのメソッドを多用します。
まずは、HTMLベースの簡単なコンソールを用意します。ドキュメントの対象となるクラスの名前をクライアントから指定するためのコンソールです。次のような簡単なコードのコンソールで問題ありません。
<html>
<head>
<b>Provide the class name to be documentated: <br /><br /></b>
</head>
<body>
<form method="get" action="generatingDocumentation.php">
<input type="text" name="class">
<input type="submit" value="Generate documentation">
<input type="reset" value="Reset">
</form>
</body>
</html>
ドキュメント生成アプリケーションの中核となるのは、「generatingDocumentation.php」のコードです。長いコードなので、以降では重要な部分だけを抜粋して示します。コード全体については、本稿のダウンロードサンプルに収録されている「generatingDocumentation.php」ファイルを参照してください。まず、ユーザーからコンソールを通じて送信されたクラス名を取得し、ファイルの拡張子を付加して、目的のクラスをインクルードします。その後で、出力ドキュメントの書き込み先となるファイルを開きます。
//get the class name to be documentated
$className=$_GET['class'];
//paste the name class with the extension .inc
$classNameExtension=$_GET['class'].".inc";
$b=$_GET['class'];
//include the $classNameExtension class
include($classNameExtension);
//get a ReflectionClass for the $className class
$reflection=new ReflectionClass($className);
//prepare the output
$hf=fopen("PTD.html","w");
次に、クラスの各部分に対して反復処理を行い、必要な情報(クラスがサポートするインターフェイス、親クラス名(ある場合)、定数、プロパティ、メソッドとその全パラメータなど)を取得します。そして、これらの情報すべてを、説明のコメントと共に、ドキュメントファイルに書き込みます。以下、クラスのメソッドとパラメータを取得してドキュメント化する部分のコードの抜粋を示します。
//get information about methods
$methods=$reflection->getMethods();
if($methods != null)
{
fwrite($hf,"¥t<tr><td align=¥"center¥"".
" colspan=¥"0¥"><font face=¥"arial¥"".
" size=¥"2¥" color=¥"purple¥">Methods:".
"</td><td align=¥"center¥" colspan=¥"0¥">".
"<font face=¥"arial¥" size=¥"2¥"".
" color=¥"black¥"><b>Name</b></td>".
"<td align=¥"center¥" colspan=¥"0¥">".
"<font face=¥"arial¥" size=¥"2¥"".
" color=¥"black¥"><b>Modifiers</b>".
"</td><td align=¥"center¥" colspan=¥"0¥">".
"<font face=¥"arial¥" size=¥"2¥"".
" color=¥"black¥"><b>Parameters</b>".
"</td><td align=¥"center¥" colspan=¥"0¥">".
"<font face=¥"arial¥" size=¥"2¥"".
" color=¥"black¥"><b>Description</b>".
"</td></tr>¥n");
foreach($methods as $in)
{
fwrite($hf,"¥t<tr><td></td><td>");
fwrite($hf,$in->getName());
if($in->isConstructor())
{ fwrite($hf," [c]"); }
fwrite($hf,"</td><td>");
if ($in->isPublic())
{ fwrite($hf,"[public]"); }
if ($in->isPrivate())
{ fwrite($hf,"[private]"); }
if ($in->isProtected())
{ fwrite($hf,"[protected]"); }
if ($in->isAbstract())
{ fwrite($hf,"[abstract]"); }
if ($in->isFinal())
{ fwrite($hf,"[final]"); }
if ($in->isStatic())
{ fwrite($hf,"[static]"); }
fwrite($hf,"</td>");
$parameters=$in->getParameters();
if($parameters != null)
{
fwrite($hf,"<td align=¥"center¥">");
$nr_parameters=count($parameters);
foreach($parameters as $out)
{
fwrite($hf,"$");
fwrite($hf,$out->getName());
if($out->isPassedByReference())
{ fwrite($hf," [&] "); }
if($out->allowsNull())
{ fwrite($hf," [+] "); }
}
fwrite($hf,"</td>");
}else { fwrite($hf,"<td></td>"); }
fwrite($hf,"<td align=¥"center¥"><i>");
fwrite($hf,$in->getDocComment());
fwrite($hf,"</i>¥n¥t</td></tr>¥n");
}
}
たとえば、このPHPドキュメントツールでリスト1のテストクラスを指定すると、図3のような情報が出力されます。
図3 PHPドキュメントツールの出力。インターフェイス、スーパークラス、定数、プロパティ、メソッドを示すドキュメントが生成される
このように、新しいリフレクションAPIはリフレクション機能の便利な実装であり、本稿のPHPドキュメントツールのような比較的複雑なアプリケーションの開発に利用できます。実行すべきクラスやメソッドをデザイン時に判断できない場合には、リフレクションは特に便利です。
経験豊富なPHP開発者。現在は、国内外のソフトウェア開発コンテストに参加するプログラミングチームの主任トレーナーを務める。国レベルの教育プロジェクト開発のコンサルティングも担当している。共著書に『XML technologies?XML in Java』があり、XML部分の執筆を担当。PHPやXMLのほか、ソフトウェアアーキテクチャ、Webサービス、UML、ハイパフォーマンスな単体テストについても関心を寄せている。