rfc:annotations
Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
rfc:annotations [2010/09/07 16:27] pierrick Add the unique instance behaviour |
rfc:annotations [2017/09/22 13:28] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Request for Comments: Class Metadata ====== | ====== Request for Comments: Class Metadata ====== | ||
+ | |||
* Version: 1.0 | * Version: 1.0 | ||
* Date: 2010-08-24 | * Date: 2010-08-24 | ||
* Author: Guilherme Blanco <guilhermeblanco@hotmail.com>, Pierrick Charron <pierrick@php.net> | * Author: Guilherme Blanco <guilhermeblanco@hotmail.com>, Pierrick Charron <pierrick@php.net> | ||
- | * Status: Ready for discussion | + | * Status: Declined |
* First Published at: http://wiki.php.net/rfc/annotations | * First Published at: http://wiki.php.net/rfc/annotations | ||
- | |||
===== Introduction ===== | ===== Introduction ===== | ||
Line 16: | Line 16: | ||
* **phpUnit** Providing meta functionality for test cases, examples: @dataProvider for test data iteration, @expectedException for catching exceptions, etc. | * **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. | * **Doctrine** For Object-Relational mapping, examples: @Entity, @OneToOne, @Id, etc. | ||
* **Zend Framework Server classes** Used to automate mappings for XML-RPC, SOAP, etc. | * **Zend Framework Server classes** Used to automate mappings for XML-RPC, SOAP, etc. | ||
+ | * **TYPO3** for dependency injection and validation | ||
+ | * **Symfony2** for routing rules | ||
* **Others** One clear thing that comes to my mind is Validation, Functional Behavior injection (which could take advantage of [[rfc:Traits]]), etc. Also, any Framework could take advantage of it somehow. | * **Others** One clear thing that comes to my mind is Validation, Functional Behavior injection (which could take advantage of [[rfc: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. | So, any meta mapping injection could be easily achieved via the implementation of a centralized Annotations support. | ||
+ | |||
+ | The .NET framework uses Data Annotation: | ||
+ | [[http://www.asp.net/mvc/tutorials/validation-with-the-data-annotation-validators-cs]] | ||
+ | |||
+ | An advantage here is the .net framework will process some annotations and inject behavior into the compiled source code. | ||
+ | |||
+ | It's important to note that annotations exist in java and .net but many strong use cases exist in these languages to provide hints to the compiler (@NotNull). | ||
+ | |||
+ | These types of use cases (hints to the Zend lexer/parser or other PHP implementations) are not presented in this RFC. | ||
===== Common Misconceptions ===== | ===== Common Misconceptions ===== | ||
Metadata mapping is commonly referred an feature that cannot be used widely, so its implementation is useless. | 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. | + | As pointed previously, there are many use cases for this support. |
+ | |||
+ | Though useful, the good and bad use cases of annotations are heavily debated (religiously): | ||
+ | |||
+ | [[http://willcode4beer.com/design.jsp?set=annotations_gotchas_best_practices]] | ||
+ | |||
+ | [[http://www.softwarereality.com/programming/annotations.jsp]] | ||
===== Proposal ===== | ===== Proposal ===== | ||
Line 37: | Line 53: | ||
When using meta mapping, less characters is preferred to speed up its construction. | When using meta mapping, less characters is preferred to speed up its construction. | ||
- | PHP Annotation could be simplified into this EBNF: | + | PHP Annotation could be simplified into this EBNF (Extended Backus-Naur Form): |
<code> | <code> | ||
Annotations ::= Annotation {Annotation}* | Annotations ::= Annotation {Annotation}* | ||
- | Annotation ::= "[" AnnotationName ["(" [Values] ")"] "]" | + | Annotation ::= "<" AnnotationName ["(" [Values] ")"] ">" |
- | AnnotationName ::= QualifiedName | SimpleName | AliasedName | + | AnnotationName ::= QualifiedName | SimpleName |
- | QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName | + | QualifiedName ::= {"\"}* NameSpacePart "\" {NameSpacePart "\"}* SimpleName |
- | AliasedName ::= Alias ":" SimpleName | + | |
NameSpacePart ::= identifier | NameSpacePart ::= identifier | ||
SimpleName ::= identifier | SimpleName ::= identifier | ||
Alias ::= identifier | Alias ::= identifier | ||
- | Values ::= Array | Value {"," Value}* | + | Values ::= Value {"," Value}* |
- | Value ::= PlainValue | FieldAssignment | + | Value ::= integer | string | float | boolean | Array | Annotation |
- | PlainValue ::= integer | string | float | boolean | Array | Annotation | + | |
- | FieldAssignment ::= FieldName "=" PlainValue | + | |
- | FieldName ::= identifier | + | |
Array ::= "array(" ArrayEntry {"," ArrayEntry}* ")" | Array ::= "array(" ArrayEntry {"," ArrayEntry}* ")" | ||
ArrayEntry ::= Value | KeyValuePair | ArrayEntry ::= Value | KeyValuePair | ||
- | KeyValuePair ::= Key "=" PlainValue | + | KeyValuePair ::= Key "=>" Value |
Key ::= string | integer | Key ::= string | integer | ||
</code> | </code> | ||
- | With identifier being the terminal: [a-zA-Z_][a-zA-Z0-9_]* | + | With integer, string, float, boolean, float and identifier being the terminals. Identifier: [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: | + | |
+ | The start/end tokens are not closed, they can be changed if anyone desires. Currently the chosen ones are: "<" for start token and ">" for end. | ||
+ | Here is an example of PHP Annotations: | ||
<code> | <code> | ||
- | [Entity(tableName="users")] | + | <Entity("users")> |
class User | class User | ||
{ | { | ||
- | [Column(type="integer")] | + | <Column("integer")> |
- | [Id] | + | <Id> |
- | [GeneratedValue(strategy="AUTO")] | + | <GeneratedValue("AUTO")> |
protected $id; | protected $id; | ||
// ... | // ... | ||
- | [ManyToMany(targetEntity="Phonenumber")] | + | <ManyToMany("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; | protected $Phonenumbers; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | One point to notice is that nested Annotations are allowed. This is an important feature against key=>value pair of what docblock implementations currently define. | ||
+ | Here is a simple sample of usage: | ||
+ | |||
+ | <code php> | ||
+ | <Simple(<Example>)> | ||
+ | class User { | ||
+ | // ... | ||
} | } | ||
</code> | </code> | ||
The support is all done through the inclusion of a new class: ReflectionAnnotation. | The support is all done through the inclusion of a new class: ReflectionAnnotation. | ||
+ | |||
+ | |||
===== How to define Annotations ===== | ===== How to define Annotations ===== | ||
- | Annotations can be defined in classes, methods, properties or functions. | + | Annotations can be defined on classes, methods, properties, functions or function parameters. |
- | ReflectionAnnotation is an abstract class the must be implemented in order to accept an Annotation definition. | + | ReflectionAnnotation is an interface that 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: | + | Once this class is extended, the subclass is ready to be used as an Annotation: |
<code php> | <code php> | ||
- | class Foo extends \ReflectionAnnotation {} | + | class Foo implements \ReflectionAnnotation { |
+ | public $value; | ||
- | [Foo(true)] | + | public function __construct($value) |
+ | { | ||
+ | $this->value = $value; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | <Foo(array(true))> | ||
class Bar { /* ... */ } | class Bar { /* ... */ } | ||
</code> | </code> | ||
- | Just extending the base class already allows you to define a unique value, accessible through a public property named "value". | + | To have access to an Annotation instance, it is done through Reflection API. |
<code php> | <code php> | ||
Line 107: | Line 131: | ||
$reflAnnot = $reflClass->getAnnotation('Foo'); | $reflAnnot = $reflClass->getAnnotation('Foo'); | ||
- | echo $foo->value; // true | + | echo $foo->value; // array(true) |
</code> | </code> | ||
- | To expand Annotations support, it is allowed to define other properties, which are required to be public. | + | To expand Annotations support, it is allowed to define other properties. |
- | BY doing it, you can define Annotations and automatically defining values to them. | + | By doing it, you can define Annotations and automatically defining values to constructor. |
<code php> | <code php> | ||
namespace App\Annotation; | namespace App\Annotation; | ||
- | class Link extends \ReflectionAnnotation { | + | class Link implements \ReflectionAnnotation { |
public $url; | public $url; | ||
public $target; | public $target; | ||
+ | | ||
+ | public function __construct($url, $target = null) | ||
+ | { | ||
+ | $this->url = $url; | ||
+ | $this->target = $target; | ||
+ | } | ||
} | } | ||
- | [App\Annotation\Link(url="http://www.php.net", target="_blank")] | + | namespace App; |
+ | |||
+ | <Annotation\Link("http://www.php.net", "_blank")> | ||
class PHPWebsite { | class PHPWebsite { | ||
- | /* ... */ | + | // ... |
} | } | ||
</code> | </code> | ||
- | ===== How to retrieve Annotation information ===== | ||
- | Annotations are only useful if defined information can be retrieved somehow. | + | Please notice that Annotations can also take advantage of "use" and "namespace" definitions. |
- | There are different ways to retrieve annotation information, mostly differing from how it was defined. | + | Example: |
- | ** The following explanation is only valid for classes ** | + | <code php> |
+ | namespace Foo\Bar; | ||
- | To define an Annotation that may be exported to subclasses, it is required to define an Annotation on ReflectionAnnotation subclass, called Inherited. | + | <Exception("I should not do this")> |
- | This is the general rule for Inherited annotations: | + | <\Exception("And also this one is ugly")> |
+ | class Playground { | ||
+ | // ... | ||
+ | } | ||
- | ** [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. ** | + | // Exception points to \Foo\Bar\Exception |
+ | // \Exception points to \Exception | ||
+ | </code> | ||
+ | ===== How to retrieve Annotation information ===== | ||
+ | |||
+ | Annotations are only useful if defined information can be retrieved somehow. | ||
Example: | Example: | ||
<code php> | <code php> | ||
- | [Inherited] | + | class Author implements \ReflectionAnnotation { |
- | class Foo extends \ReflectionAnnotation {} | + | public $name; |
+ | |||
+ | public function __construct($name) { $this->name = $name; } | ||
+ | } | ||
- | class Bar extends \ReflectionAnnotation {} | + | <Author("Pierrick Charron")> |
- | + | class MyTest { | |
- | [Foo] | + | <Author("Guilherme Blanco")> |
- | [Bar] | + | public function __toString() |
- | class A {} | + | { |
+ | // ... | ||
+ | } | ||
+ | } | ||
- | class B extends A {} | + | class ExtendedTest extends MyTest {} |
</code> | </code> | ||
Line 156: | Line 202: | ||
<code php> | <code php> | ||
- | $reflClassA = new \ReflectionClass('A'); | + | $reflClassA = new \ReflectionClass('MyTest'); |
var_dump($reflClassA->getAnnotations()); | var_dump($reflClassA->getAnnotations()); | ||
/* | /* | ||
- | array(2) { | + | array(1) { |
- | ["Foo"]=> | + | ["Author"]=> |
- | object(Foo)#%d (1) { | + | object(Author)#%d (1) { |
- | ["value"]=>NULL | + | ["name"]=> string (16) "Pierrick Charron" |
- | }, | + | |
- | ["Bar"]=> | + | |
- | object(Bar)#%d (1) { | + | |
- | ["value"]=>NULL | + | |
} | } | ||
} | } | ||
*/ | */ | ||
- | $reflClassB = new \ReflectionClass('B'); | + | $reflMethodToString = $reflClassA->getMethod('__toString'); |
- | var_dump($reflClassB->getAnnotations()); | + | var_dump($reflMethodToString->getAnnotations()); |
/* | /* | ||
- | array(2) { | + | array(1) { |
- | ["Foo"]=> | + | ["Author"]=> |
- | object(Foo)#%d (1) { | + | object(Author)#%d (1) { |
- | ["value"]=>NULL | + | ["name"]=> string (16) "Guilherme Blanco" |
} | } | ||
+ | } | ||
+ | */ | ||
+ | |||
+ | $reflClassB = new \ReflectionClass('ExtendedTest'); | ||
+ | var_dump($reflClassB->getAnnotations()); | ||
+ | /* | ||
+ | array(0) { | ||
} | } | ||
*/ | */ | ||
</code> | </code> | ||
- | The method `getAnnotations()` supports one of the three arguments: | + | Please notice that multiple instantiation of same Annotation is left intentionally for discussion. It could be supported. |
+ | Also, the inheritance of Annotations is left for discussion too. This can be done by 2 different approaches: | ||
- | * \ReflectionAnnotation::ALL - Fetched both defined annotations + inherited ones | + | * Using an Annotation at the top of ReflectionAnnotation definition. This was it was working on first patch. |
- | * \ReflectionAnnotation::INHERITED - Retrieves only inherited annotations | + | * Using a different interface to be implemented. This was another approach that we considered, but left for discussion. |
- | * \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. | Another available method is to retrieve an specific Annotation: `getAnnotation($name)`, which may return the matched Annotation or null if not found. | ||
Line 196: | Line 245: | ||
<code php> | <code php> | ||
- | + | interface ReflectionAnnotation { | |
- | 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; | + | |
- | } | + | |
- | } | + | |
- | } | + | |
} | } | ||
Line 224: | Line 259: | ||
// ... | // ... | ||
- | public function getAnnotations($type = ReflectionAnnotation::ALL); | + | public function getAnnotations(); |
- | public function getAnnotation($name, $type = ReflectionAnnotation::ALL); | + | public function getAnnotation($name); |
- | public function hasAnnotation($name, $type = ReflectionAnnotation::ALL); | + | public function hasAnnotation($name); |
} | } | ||
Line 238: | Line 273: | ||
class ReflectionMethod { | class ReflectionMethod { | ||
+ | // ... | ||
+ | |||
+ | public function getAnnotations(); | ||
+ | public function getAnnotation($name); | ||
+ | public function hasAnnotation($name); | ||
+ | } | ||
+ | |||
+ | class ReflectionParameter { | ||
// ... | // ... | ||
Line 248: | Line 291: | ||
===== BC breaks ===== | ===== BC breaks ===== | ||
- | * Creates two additional classes named "ReflectionAnnotation" and "Inherited" that may break existing code. | + | * Creates one additional classes named "ReflectionAnnotation" that may break existing code. |
* None otherwise (no new keywords) | * None otherwise (no new keywords) | ||
+ | ===== To be discussed ===== | ||
+ | |||
+ | * Possible usage of "annotation" as keyword instead of an abstract class. | ||
+ | * Tokens for start/end. Currently it's "<" and ">". | ||
+ | * Nested Annotation declaration: <Foo(<Bar>)> or <Foo(new Bar)> (this changes the EBNF if any change is agreed). Currently the supported one is: <Foo(<Bar>)> | ||
+ | * Multiple instantiations of Annotation classes on a same block. | ||
+ | * Inheritance of classes/properties/method and Annotations declarations. | ||
===== Patch ===== | ===== Patch ===== | ||
- | [[http://www.adoy.net/php/Annotations.diff]] | + | Patch : [[http://www.adoy.net/php/Annotations-v3.diff]] |
+ | |||
+ | Tests : [[http://www.adoy.net/php/Annotations-v3-tests.diff]] | ||
===== Changelog ===== | ===== Changelog ===== | ||
Line 261: | Line 313: | ||
* 2010-08-24 guilhermeblanco Updated for a real doable support | * 2010-08-24 guilhermeblanco Updated for a real doable support | ||
* 2010-08-24 pierrick Add the patch | * 2010-08-24 pierrick Add the patch | ||
+ | * 2011-05-09 guilhermeblanco Updated patch with recent compatibility. Previous patch removed. New one should be added shortly. |
rfc/annotations.1283876829.txt.gz · Last modified: 2017/09/22 13:28 (external edit)