Request for Comments: Class Metadataを和訳してみた
ざっくりですが。PHPでここまでやるか?というのは置いといて、面白そうなネタではあります。ホントにやるのかなぁ。。。?
訳の対象は、2010/08/24付けのVersion 1.0です。間違いがあれば、指摘してください。
導入
多くの言語が、現在メタデータ情報をサポートしている。このRFCは、多くのアプリケーションに利益があるこの強力なツールがPHPでどう実装されるかについてのアイデアを示す。
なぜクラスメタデータが必要なのか?
一般的に、フレームワークは正しく動作するためにメタデータ情報を利用している。それらは様々な目的のために使われる。
- phpUnit:テストケース用の機能を提供する。たとえば、@dataProviderはテストデータのイテレーションのため、@expectedExceptionは例外をキャッチするため、など
- phpDoc:APIを生成するための有益な情報を提供する。たとえば、@author, @param, @returnなど。
- Doctrine:ORマッピング用。たとえば、@Entity, @OneToOne, @Idなど。
- Zend Framework Server classes:XML-RPC、SOAPなどで自動マッピングに使われる。
- その他:思い浮かぶのは、バリデーションや機能的な振る舞いのインジェクション(Traitsをうまく利用できるかも)など。また、あるフレームワークは、何らかの形でうまく利用できるかも知れない。
提案
最初に決定するものは、アノテーションをカテゴライズするためのトークンだろう。
メタマッピングを利用する場合、速度を上げるため、より少ない文字を使用するのが好まれる。
PHPアノテーションは、次のEBNFに単純化できる。
Annotations ::= Annotation {Annotation}* Annotation ::= "[" AnnotationName ["(" [Values] ")"] "]" AnnotationName ::= QualifiedName | SimpleName | AliasedName QualifiedName ::= {"\"}* NameSpacePart "\" {NameSpacePart "\"}* SimpleName AliasedName ::= Alias ":" SimpleName NameSpacePart ::= identifier SimpleName ::= identifier Alias ::= identifier Values ::= Array | Value {"," Value}* Value ::= PlainValue | FieldAssignment PlainValue ::= integer | string | float | boolean | Array | Annotation FieldAssignment ::= FieldName "=" PlainValue FieldName ::= identifier Array ::= "array(" ArrayEntry {"," ArrayEntry}* ")" ArrayEntry ::= Value | KeyValuePair KeyValuePair ::= Key "=>" PlainValue Key ::= string | integer
identifierは[a-zA-Z_][a-zA-Z0-9_]*。アノテーションは開始/終了のトークンで構成される。次はPHPアノテーションの例である。
<?php [Entity(tableName="users")] class User { [Column(type="integer")] [Id] [GeneratedValue(strategy="AUTO")] protected $id; // ... [ManyToMany(targetEntity="Phonenumber")] [JoinTable( name="users_phonenumbers", joinColumns=array( [JoinColumn(name="user_id", referencedColumnName="id")] ), inverseJoinColumns=array( [JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)] ) )] protected $Phonenumbers; }
このサポートは、新しいクラス ReflectionAnnotation の導入によって行われる。
アノテーションの定義の仕方
アノテーションは、クラス、メソッド、プロパティそして関数として定義される。ReflectionAnnotationは抽象クラスで、アノテーションの定義を受け入れるための実装を行う必要がある。一度このクラスが拡張すれば、そのサブクラスはアノテーションとして使う準備が整う。
class Foo extends \ReflectionAnnotation {} [Foo(true)] class Bar { /* ... */ }
ベースクラスを拡張しただけで、一意の値を定義することができる。その値は、publicプロパティ "value" としてアクセスできる。
$reflClass = new \ReflectionClass('Bar'); $reflAnnot = $reflClass->getAnnotation('Foo'); echo $foo->value; // true
アノテーションを拡張するには、他のプロパティを定義する。それらはpublicである必要がある。これをすることで、アノテーションを定義でき、自動的に値をそれらに代入できる。
<?php namespace App\Annotation; class Link extends \ReflectionAnnotation { public $url; public $target; } [App\Annotation\Link(url="http://www.php.net", target="_blank")] class PHPWebsite { /* ... */ }
アノテーション情報の扱われ方
アノテーションは、何らかの処理がされる情報が定義された場合のみ有用である。アノテーション情報の扱われ方はいくつかあるが、どう定義されたかによっても異なる。
次の説明は、クラスに対してのみ有効である。
サブクラスにエクスポートされるアノテーションを定義するには、"Inherited" というアノテーションを定義した ReflectionAnnotation のサブクラスが必要になる。次は、継承されたアノテーションの一般的なルールである。
[Inherited] クラス以外の何かに付けられたアノテーションは継承されない。1つ以上のインターフェースを実装しているクラスは、実装しているインターフェースのどのアノテーションも継承しない
たとえば。
<?php [Inherited] class Foo extends \ReflectionAnnotation {} class Bar extends \ReflectionAnnotation {} [Foo] [Bar] class A {} class B extends A {}
クラスAとBに定義された情報を処理する場合、次の結果を得る。
<?php $reflClassA = new \ReflectionClass('A'); var_dump($reflClassA->getAnnotations()); /* array(2) { ["Foo"]=> object(Foo)#%d (1) { ["value"]=>NULL }, ["Bar"]=> object(Bar)#%d (1) { ["value"]=>NULL } } */ $reflClassB = new \ReflectionClass('B'); var_dump($reflClassB->getAnnotations()); /* array(2) { ["Foo"]=> object(Foo)#%d (1) { ["value"]=>NULL } } */
メソッド "getAnnotations()" は、次の3つのうち、1つをサポートする。
- \ReflectionAnnotation::ALL - 定義されたアノテーションと継承されたアノテーションを取得する
- \ReflectionAnnotation::INHERITED - 継承されたアノテーションを取得する
- \ReflectionAnnotation::DEFINED - 定義されたアノテーションのみを取得する ([Inherited]として定義されているが、当該クラスで宣言されている場合とか)
他の利用可能なメソッドには、特定のアノテーションを処理する "getAnnotation($name)" がある。これは、マッチしたアノテーションを返す、もしくは、見つからない場合にNULLを返す。
単一コードの要素(プロパティ、クラス、メソッドなど)のレベルでは、常に与えられたアノテーションの単一インスタンスを取得することになる。これは、getAnnotation を同一要素に対して複数回呼び出した場合、常に同じインスタンスを得る、ということを意味する。
これらは、基本的に、リフレクションAPIの拡張メソッドである。生のPHPで書くとの通り。
<?php abstract class ReflectionAnnotation { const INHERITED = 1; const DECLARED = 2; const ALL = 3; public $value = null; public function __construct(Reflector $reflector, array $properties = null) { if (is_array($properties)) { foreach ($properties as $k => $v) { $this->$k = $v; } } } } class ReflectionFunction { // ... public function getAnnotations(); public function getAnnotation($name); public function hasAnnotation($name); } class ReflectionClass { // ... public function getAnnotations($type = ReflectionAnnotation::ALL); public function getAnnotation($name, $type = ReflectionAnnotation::ALL); public function hasAnnotation($name, $type = ReflectionAnnotation::ALL); } class ReflectionProperty { // ... public function getAnnotations(); public function getAnnotation($name); public function hasAnnotation($name); } class ReflectionMethod { // ... public function getAnnotations(); public function getAnnotation($name); public function hasAnnotation($name); }
後方互換性の喪失
- 追加される2つのクラス ReflectionAnnotation と Inherited が既存コードに影響するかも
- 他は特に無し(新しいキーワードはない)
更新履歴
- 2010-05-26 guilhermeblanco Initial RFC creation.
- 2010-08-24 guilhermeblanco Updated for a real doable support
- 2010-08-24 pierrick Add the patch