rfc:attributes
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:attributes [2016/04/21 10:46] – created dmitry | rfc:attributes [2016/05/23 23:06] – dmitry | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Attributes ====== | ====== PHP RFC: Attributes ====== | ||
- | * Version: | + | * Version: |
* Date: 2016-04-21 | * Date: 2016-04-21 | ||
* Author: Dmitry Stogov, dmitry@zend.com | * Author: Dmitry Stogov, dmitry@zend.com | ||
- | * Status: | + | * Status: Declined |
* First Published at: http:// | * First Published at: http:// | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | Attributes (or annotation) is a form of syntactic metadata that can be added to language classes, functions, etc. PHP offers only a single form of such metadata - doc-comments. This is just a string and to keep some structured information, | + | Attributes (or annotations) are a form of syntactic metadata that can be added to language classes, functions, etc. PHP offers only a single form of such metadata - doc-comments. This is just a string and to keep some structured information, |
- | Many languages like Java, C#, Hack, etc provide a simpler way. They allow definition of structured meta-information through small syntax extension. | + | Many languages like Java, C#, Hack, etc provide a simpler way. They allow the definition of structured meta-information through small syntax extension. |
===== Proposal ===== | ===== Proposal ===== | ||
==== Attribute Syntax ==== | ==== Attribute Syntax ==== | ||
- | Attribute | + | < |
- | < | + | < |
<< | << | ||
<< | << | ||
Line 22: | Line 22: | ||
</ | </ | ||
- | Each attribute definition construct may also define one or few named attributes, each attribute may be used without | + | Each attribute definition construct may also define one or more named attribute, which may be used with no value, |
< | < | ||
- | < | + | < |
- | { "," | + | { "," |
- | < | + | <namespace-name> |
< | < | ||
</ | </ | ||
Line 33: | Line 33: | ||
And Example: | And Example: | ||
- | < | + | < |
<< | << | ||
function foo() {} | function foo() {} | ||
</ | </ | ||
- | It's not possible to use the same attribute name for the same definition | + | It's not possible to use the same attribute name more than once on the same definition, however, it's possible to use multiple attribute values |
- | < | + | < |
<< | << | ||
function foo() {} | function foo() {} | ||
Line 50: | Line 50: | ||
==== Arbitrary PHP Expressions as Attribute Values (AST attributes) ==== | ==== Arbitrary PHP Expressions as Attribute Values (AST attributes) ==== | ||
- | Except for simple scalars, attribute values may be represented with any valid PHP expression. | + | Other than simple scalars, attribute values may be represented with any valid PHP expression. |
- | < | + | < |
<< | << | ||
function foo($a, $b) { | function foo($a, $b) { | ||
Line 58: | Line 58: | ||
</ | </ | ||
- | In this case, internally, the value of attribute is kept as an Abstract Syntax Tree, and we will able to read every individual node of this tree separately. This approach implies usage of the same PHP syntax for meta data and eliminates need for separate parser. | + | In this case, internally, the value of the attribute is kept as an Abstract Syntax Tree, and the user will have the ability |
+ | |||
+ | The native usage of an AST is not especially necessary. It's also possible to use plain strings and then transform them into AST at user level, through the php-ast extension. | ||
+ | |||
+ | <code php> | ||
+ | << | ||
+ | function foo($a, $b) { | ||
+ | } | ||
+ | $r = new ReflectionFunction(" | ||
+ | $ast = ast\parse_code($r-> | ||
+ | </ | ||
==== Reflection ==== | ==== Reflection ==== | ||
- | Few reflection | + | Reflection |
- | < | + | < |
function ReflectionFunction:: | function ReflectionFunction:: | ||
function ReflectionClass:: | function ReflectionClass:: | ||
function ReflectionProperty:: | function ReflectionProperty:: | ||
- | function | + | function |
</ | </ | ||
- | These functions return | + | These functions return |
- | < | + | < |
<< | << | ||
function foo() {} | function foo() {} | ||
Line 83: | Line 93: | ||
array(3) { | array(3) { | ||
[" | [" | ||
- | | + | |
+ | } | ||
[" | [" | ||
- | int(0) | + | |
+ | [0]=> | ||
+ | | ||
+ | } | ||
[" | [" | ||
array(2) { | array(2) { | ||
Line 97: | Line 111: | ||
==== AST Representation ==== | ==== AST Representation ==== | ||
- | While internally AST is stored in native zend_ast format, Reflection*:: | + | While internally AST is stored in native zend_ast format, Reflection*:: |
- | < | + | < |
<< | << | ||
function foo($a, $b) { | function foo($a, $b) { | ||
Line 110: | Line 124: | ||
array(1) { | array(1) { | ||
[" | [" | ||
- | object(ast\Node)# | + | |
- | [" | + | [0]=> |
- | int(521) | + | |
- | [" | + | [" |
- | int(0) | + | int(521) |
- | [" | + | [" |
- | int(0) | + | int(0) |
- | [" | + | [" |
- | array(2) { | + | int(0) |
- | [0]=> | + | [" |
- | object(ast\Node)# | + | array(2) { |
- | [" | + | [0]=> |
- | int(520) | + | object(ast\Node)# |
- | [" | + | [" |
- | int(1) | + | int(520) |
- | [" | + | [" |
- | int(0) | + | int(1) |
- | [" | + | [" |
- | array(2) { | + | int(0) |
- | [0]=> | + | [" |
- | object(ast\Node)# | + | array(2) { |
- | [" | + | [0]=> |
- | int(256) | + | object(ast\Node)# |
- | [" | + | [" |
- | int(0) | + | int(256) |
- | [" | + | [" |
- | int(0) | + | int(0) |
- | [" | + | [" |
- | array(1) { | + | int(0) |
- | [0]=> | + | [" |
- | string(1) "a" | + | array(1) { |
+ | [0]=> | ||
+ | string(1) " | ||
+ | | ||
+ | } | ||
+ | | ||
+ | object(ast\Node)# | ||
+ | [" | ||
+ | int(256) | ||
+ | [" | ||
+ | int(0) | ||
+ | [" | ||
+ | | ||
+ | [" | ||
+ | array(1) { | ||
+ | [0]=> | ||
+ | | ||
+ | } | ||
} | } | ||
} | } | ||
- | | + | } |
- | | + | |
- | ["kind" | + | int(0) |
- | int(256) | + | } |
- | ["flags" | + | } |
- | int(0) | + | } |
- | ["lineno" | + | } |
- | int(0) | + | </ |
- | ["children"]=> | + | |
- | | + | [[https:// |
- | [0]=> | + | |
- | | + | ==== Use Cases ==== |
+ | |||
+ | With attributes, it's extremely simple to mark some functions with some specific " | ||
+ | |||
+ | <code php> | ||
+ | << | ||
+ | function add(int $a, $int $b): int { | ||
+ | return $a + $b; | ||
+ | } | ||
+ | |||
+ | << | ||
+ | function foo() { | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Attributes may be used as a base level for an annotation system similar to Doctrine, where each attribute is represented by an object of corresponding class that perform validation and other actions. | ||
+ | |||
+ | <code php> | ||
+ | <?php | ||
+ | namespace Doctrine\ORM { | ||
+ | |||
+ | class Entity { | ||
+ | private $name; | ||
+ | public function __construct($name) { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function GetClassAttributes($class_name) { | ||
+ | $reflClass = new \ReflectionClass($class_name); | ||
+ | $attrs = $reflClass-> | ||
+ | foreach ($attrs as $name => & | ||
+ | $name = " | ||
+ | $values = new $name(...$values); | ||
+ | } | ||
+ | return $attrs; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | namespace { | ||
+ | << | ||
+ | class User {} | ||
+ | |||
+ | var_dump(Doctrine\ORM\GetClassAttributes(" | ||
+ | } | ||
+ | ?> | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | array(1) { | ||
+ | | ||
+ | | ||
+ | ["name":" | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Attributes with AST values may be used to implement " | ||
+ | |||
+ | <code php> | ||
+ | << | ||
+ | $a >= 0, | ||
+ | $b >= 0, | ||
+ | $c >= 0, | ||
+ | $a <= ($b+$c), | ||
+ | $b <= ($a+$c), | ||
+ | $c <= ($a+$b))>> | ||
+ | << | ||
+ | function triangleArea($a, | ||
+ | { | ||
+ | $halfPerimeter = ($a + $b + $c) / 2; | ||
+ | |||
+ | return sqrt($halfPerimeter | ||
+ | * ($halfPerimeter - $a) | ||
+ | * ($halfPerimeter - $b) | ||
+ | * ($halfPerimeter - $c)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Special Attributes ==== | ||
+ | |||
+ | < | ||
+ | |||
+ | ==== Criticism and Alternative Approaches ==== | ||
+ | |||
+ | === Doc-comments === | ||
+ | |||
+ | Today we are using single doc-comments for any kind of meta-information, | ||
+ | |||
+ | <code php> | ||
+ | /** | ||
+ | * Compute area of a triangle | ||
+ | * | ||
+ | * This function computes the area of a triangle using Heron' | ||
+ | * | ||
+ | * @param number $a Length of 1st side | ||
+ | * @requires ($a >= 0) | ||
+ | * @param number $b Length of 2nd side | ||
+ | * @requires ($b >= 0) | ||
+ | * @param number $c Length of 3rd side | ||
+ | * @requires ($c >= 0) | ||
+ | * @requires ($a <= ($b+$c)) | ||
+ | * @requires ($b <= ($a+$c)) | ||
+ | * @requires ($c <= ($a+$b)) | ||
+ | * | ||
+ | * @return number The triangle area | ||
+ | * @ensures (RET >= 0) | ||
+ | * | ||
+ | * @jit | ||
+ | */ | ||
+ | |||
+ | function triangleArea($a, | ||
+ | { | ||
+ | $halfPerimeter = ($a + $b + $c) / 2; | ||
+ | |||
+ | return sqrt($halfPerimeter | ||
+ | * ($halfPerimeter - $a) | ||
+ | * ($halfPerimeter - $b) | ||
+ | * ($halfPerimeter - $c)); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This approach works, but PHP itself doesn' | ||
+ | e.g. to check " | ||
+ | |||
+ | It might be possible to make PHP parse existing doc-comments and keep information as structured attributes, but we would need to invoke additional parser for each doc-comment; | ||
+ | |||
+ | === Full Featured Annotation System (like Doctrine) === | ||
+ | |||
+ | This RFC proposes only base PHP attribute functionality. It doesn' | ||
+ | |||
+ | <code php> | ||
+ | /** | ||
+ | * @Block( | ||
+ | * id = " | ||
+ | * | ||
+ | * ) | ||
+ | */ | ||
+ | |||
+ | << | ||
+ | "id" | ||
+ | " | ||
+ | ]))>> | ||
+ | class PageTitleBlock { | ||
+ | } | ||
+ | |||
+ | function TranslateDrupalAttribute($value) { | ||
+ | if ($value instanceof \ast\Node) { | ||
+ | if ($value-> | ||
+ | $a = $value-> | ||
+ | if (is_string($a) && class_exists($a)) { | ||
+ | $value = new $a; | ||
+ | } else if ($a instanceof \ast\Node && | ||
+ | | ||
+ | | ||
+ | | ||
+ | class_exists($a-> | ||
+ | | ||
+ | | ||
+ | | ||
+ | $args = $a-> | ||
+ | if ($args instanceof ast\Node && $args-> | ||
+ | $obj = new $a-> | ||
+ | foreach ($args-> | ||
+ | | ||
+ | $arg-> | ||
+ | count($arg-> | ||
+ | is_string($arg-> | ||
+ | | ||
+ | $val = $arg->children[0]; | ||
+ | | ||
+ | $obj-> | ||
+ | } else { | ||
+ | $obj-> | ||
+ | } | ||
+ | } else { | ||
+ | throw DrupalAnnotationError("..."); | ||
} | } | ||
} | } | ||
+ | } else { | ||
+ | $name = $a-> | ||
+ | $obj = new $name($args); | ||
} | } | ||
+ | $value = $obj; | ||
+ | } else { | ||
+ | throw DrupalAnnotationError(" | ||
+ | } | ||
+ | } else { | ||
+ | throw DrupalAnnotationError(" | ||
+ | } | ||
+ | } | ||
+ | return $value; | ||
+ | } | ||
+ | |||
+ | function GetDrupalAnnotations($class_name) { | ||
+ | $reflClass = new \ReflectionClass($class_name); | ||
+ | $attrs = $reflClass-> | ||
+ | $ret = []; | ||
+ | foreach ($attrs as $name => $values) { | ||
+ | if ($name == " | ||
+ | foreach ($values as & | ||
+ | $ret[] = TranslateDrupalAttribute($value); | ||
} | } | ||
- | [1]=> | ||
- | int(0) | ||
} | } | ||
} | } | ||
+ | return $ret; | ||
} | } | ||
+ | |||
+ | class Block {} | ||
+ | class Translation { | ||
+ | public $text; | ||
+ | function __construct($text) { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | var_dump(GetDrupalAnnotations(" | ||
</ | </ | ||
+ | |||
+ | < | ||
+ | array(1) { | ||
+ | [0]=> | ||
+ | object(Block)# | ||
+ | [" | ||
+ | string(21) " | ||
+ | [" | ||
+ | object(Translation)# | ||
+ | [" | ||
+ | string(13) "Site branding" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === ' | ||
+ | |||
+ | ' | ||
+ | |||
+ | === Naming (attributes or annotations) === | ||
+ | |||
+ | Different programming languages use different terms for similar features. Some use annotation, some attributes. I prefer name " | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | None | + | The RFC doesn' |
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 177: | Line 440: | ||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | [[https:// | + | [[https:// |
==== To Opcache ==== | ==== To Opcache ==== | ||
Line 189: | Line 452: | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
- | None. | + | * part of patch related to new AST classes (zend_ast.*) might need to be slightly changed to satisfy need of attributes and php-ast in best way. |
+ | * < | ||
+ | * < | ||
+ | * < | ||
+ | * < | ||
+ | * < | ||
+ | * < | ||
+ | |||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | This RFC modifies the PHP language syntax | + | The voting started on May 10th, 2016 and will close on May 24th, 2016. |
+ | |||
+ | <doodle title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | ---- | ||
+ | |||
+ | <doodle title=" | ||
+ | * valid PHP expression (internally represented as AST) | ||
+ | * valid PHP constant (number or string) | ||
+ | </ | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
Line 206: | Line 487: | ||
* [[https:// | * [[https:// | ||
* [[https:// | * [[https:// | ||
+ | * [[https:// | ||
+ | * [[https:// | ||
- | ===== Rejected Features ===== | ||
- | Keep this updated with features that were discussed on the mail lists. |
rfc/attributes.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1