This is an old revision of the document!
Request for Comments: Class Metadata
- Version: 1.0
- Date: 2010-08-24
- Author: Guilherme Blanco guilhermeblanco@hotmail.com, Pierrick Charron pierrick@php.net
- Status: Ready for discussion
- First Published at: http://wiki.php.net/rfc/annotations
Introduction
Many languages currently support metadata information. This RFC exposes an idea about how can it be implemented in PHP, providing a powerful tool that many applications can take advantage of.
Why do we need Class Metadata?
Frameworks in general rely on metadata information in order to correctly work. They can use it for many purposes:
- phpUnit Providing meta functionality for test cases, examples: @dataProvider for test data iteration, @expectedException for catching exceptions, etc.
- phpDoc Providing useful information for API generation, examples: @author, @param, @return, etc.
- Doctrine For Object-Relational mapping, examples: @Entity, @OneToOne, @Id, etc.
- Zend Framework Server classes Used to automate mappings for XML-RPC, SOAP, etc.
- Others One clear thing that comes to my mind is Validation, Functional Behavior injection (which could take advantage of Traits), etc. Also, any Framework could take advantage of it somehow.
So, any meta mapping injection could be easily achieved via the implementation of a centralized Annotations support.
Common Misconceptions
Metadata mapping is commonly referred an feature that cannot be used widely, so its implementation is useless. As pointed previously, there're many use cases for this support.
Proposal
First thing to be decided would be the tokens to be used for categorize an Annotation.
- Java uses Annotations
- C# uses Attributes
When using meta mapping, less characters is preferred to speed up its construction.
PHP Annotation could be simplified into this 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 ::= "{" ArrayEntry {"," ArrayEntry}* "}" ArrayEntry ::= Value | KeyValuePair KeyValuePair ::= Key "=" PlainValue Key ::= string | integer
With identifier being the terminal: [a-zA-Z_][a-zA-Z0-9_]* The annotation can be a composite of a start/end tokens. Here is an example of PHP Annotations:
[Entity(tableName="users")] class User { [Column(type="integer")] [Id] [GeneratedValue(strategy="AUTO")] protected $id; // ... [ManyToMany(targetEntity="Phonenumber")] [JoinTable( name="users_phonenumbers", joinColumns={ [JoinColumn(name="user_id", referencedColumnName="id")] }, inverseJoinColumns={ [JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)] } )] protected $Phonenumbers; }
The support is all done through the inclusion of a new class: ReflectionAnnotation.
How to define Annotations
Annotations can be defined in classes, methods, properties or functions. ReflectionAnnotation is an abstract class the must be implemented in order to accept an Annotation definition. Once this class is extended, the subclass is ready to be used an an Annotation:
class Foo extends \ReflectionAnnotation {} [Foo(true)] class Bar { /* ... */ }
Just extending the base class already allows you to define a unique value, accessible through a public property named “value”.
$reflClass = new \ReflectionClass('Bar'); $reflAnnot = $reflClass->getAnnotation('Foo'); echo $foo->value; // true
To expand Annotations support, it is allowed to define other properties, which are required to be public. BY doing it, you can define Annotations and automatically defining values to them.
namespace App\Annnotation; class Link extends \ReflectionClass { public $url; public $target; } [App\Annotation\Link(url="http://www.php.net", target="_blank")] class PHPWebsite { /* ... */ }
How to retrieve Annotation information
Annotations are only useful if defined information can be retrieved somehow. There are different ways to retrieve annotation information, mostly differing from how it was defined.
The following explanation is only valid for classes
To define an Annotation that may be exported to subclasses, it is required to define an Annotation on ReflectionAnnotation subclass, called Inherited. This is the general rule for Inherited annotations:
[Inherited] annotations are not inherited when used to annotate anything other than a class. A class that implements one or more interfaces never inherits any annotations from the interfaces it implements.
Example:
[Inherited] class Foo extends \ReflectionAnnotation {} class Bar extends \ReflectionAnnotation {} [Foo] [Bar] class A {} class B extends A {}
When you attempt to retrieve the defined information for classes A and B, you get:
$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 } } */
The method `getAnnotations()` supports one of the three arguments:
- \ReflectionAnnotation::ALL - Fetched both defined annotations + inherited ones
- \ReflectionAnnotation::INHERITED - Retrieves only inherited annotations
- \ReflectionAnnotation::DEFINED - Retrieves only defined annotations (which may be defined as [Inherited], but declared in current class)
Another available method is to retrieve an specific Annotation: `getAnnotation($name)`, which may return the matched Annotation or null if not found.
Basically, these are the extended methods in Reflection API, written in raw PHP:
abstract class ReflectionAnnotation { const INHERITED = 1; const DECLARED = 2; const ALL = 3; public $value = null; public function __construct(array $properties) { 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); }
Patch
Changelog
- 2010-05-26 guilhermeblanco Initial RFC creation.
- 2010-08-24 guilhermeblanco Updated for a real doable support
- 2010-08-24 pierrick Add the patch