rfc:attributes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:attributes [2016/04/25 02:51] – fixed typo stasrfc:attributes [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== PHP RFC: Attributes ====== ====== PHP RFC: Attributes ======
-  * Version: 0.9+  * Version: 1.0
   * Date: 2016-04-21   * Date: 2016-04-21
   * Author: Dmitry Stogov, dmitry@zend.com   * Author: Dmitry Stogov, dmitry@zend.com
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: http://wiki.php.net/rfc/attributes   * First Published at: http://wiki.php.net/rfc/attributes
  
 ===== Introduction ===== ===== Introduction =====
-Attributes (or annotationis 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, we had to use some pseudo-language. Then we has to parse it to access particular element of that structure.+Attributes (or annotationsare 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, we had to use some pseudo-language. Then we have to parse it to access particular element of that structure.
  
-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 ====
-<nowiki>Attribute is a specially formatted text enclosed with "<<" and ">>". Attributes may be applied to functions, classes, interfaces, traits, methods, properties and class constants. In the same way as doc-comments, attributes should be placed before the corresponding definition, but in opposite, it is possible to define few attributes for the same declaration.</nowiki>+<nowiki>An attribute is a specially formatted text enclosed with "<<" and ">>". Attributes may be applied to functions, classes, interfaces, traits, methods, properties and class constants. In the same way as doc-comments, attributes must be placed before the corresponding definition, but it'possible to define more than one attribute on the same declaration.</nowiki>
  
 <code php> <code php>
Line 22: Line 22:
 </code> </code>
  
-Each attribute definition construct may also define one or few named attributes, each attribute may be used without value, with single value or multiple values. See the EBNF:+Each attribute definition construct may also define one or more named attribute, which may be used with no value, single value or multiple values. See the EBNF grammar:
  
 <code> <code>
-<attribute> ::= "<<" <name> [ "(" <value> { "," <value> } ")"+<attribute> ::= "<<" <namespace-name> [ "(" <value> { "," <value> } ")"
-                { "," <name> [ "(" <value> { "," <value> } ")" ] } ">>"+                { "," <namespace-name> [ "(" <value> { "," <value> } ")" ] } ">>"
-<name>      ::= STRING.+<namespace-name>      ::= STRING.
 <value>     ::= <php-constant> | <php-expression>. <value>     ::= <php-constant> | <php-expression>.
 </code> </code>
Line 38: Line 38:
 </code> </code>
  
-It's not possible to use the same attribute name for the same definition few times, however it's possible to use multiple attribute values.+It's not possible to use the same attribute name more than once on the same definition, howeverit's possible to use multiple attribute values associated with this name.
  
 <code php> <code php>
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.
  
 <code php> <code php>
Line 58: Line 58:
 </code> </code>
  
-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 to read every individual node of this tree separately. This approach implies the usage of the same PHP syntax for meta data and eliminates the need for separate parser. 
 + 
 +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> 
 +<<test("$a + $b > 0")>> 
 +function foo($a, $b) { 
 +
 +$r = new ReflectionFunction("foo"); 
 +$ast = ast\parse_code($r->getAttributes()["test"][0]); 
 +</code>
  
 ==== Reflection ==== ==== Reflection ====
  
-Few reflection classes are extended with getAttributes() methodhat returns array of attributes.+Reflection classes are extended with the getAttributes() methodsand return array of attributes.
  
 <code php> <code php>
Line 71: Line 81:
 </code> </code>
  
-These functions return false if there were no attributes defined. Otherwise they return array with attribute names as keys and corresponding values. Attributes without values represented as boolean **true**, attributes with single values by the value itselfand with multiple values by the array of the values.+These functions return empty array if there were no attributes defined. Otherwisethey return an array with attribute names as keys and nested arrays as the corresponding values. Attributes without values represented by empty arrays, attributes with single value by arrays with a single elementetc.
  
 <code php> <code php>
Line 83: Line 93:
 array(3) { array(3) {
   ["WithoutValue"]=>   ["WithoutValue"]=>
-  bool(true)+  array(0
 +  }
   ["SingleValue"]=>   ["SingleValue"]=>
-  int(0)+  array(1) { 
 +    [0]=> 
 +    int(0) 
 +  }
   ["FewValues"]=>   ["FewValues"]=>
   array(2) {   array(2) {
Line 97: Line 111:
 ==== AST Representation ==== ==== AST Representation ====
  
-While internally AST is stored in native zend_ast format, Reflection*::getAttributes() methods return the corresponding representation built with objects of \ast\Node and \ast\Node\Decl classes, borrowed from [[https://github.com/nikic/php-ast|php-ast]]. These classes moved onto PHP core and may be used even without php-ast extension. However, it also defines useful constants and functions, that would simplify work with AST in PHP.+While internally AST is stored in native zend_ast format, Reflection*::getAttributes() methods return the corresponding representation built with objects of \ast\Node and \ast\Node\Decl classes, borrowed from [[https://github.com/nikic/php-ast|php-ast]]. These classes moved onto PHP core may be used even without php-ast extension. However, it also defines useful constants and functions, that would simplify work with AST in PHP.
  
 <code php> <code php>
Line 110: Line 124:
 array(1) { array(1) {
   ["test"]=>   ["test"]=>
-  object(ast\Node)#2 (4) { +  array(1) { 
-    ["kind"]=> +    [0]=> 
-    int(521) +    object(ast\Node)#2 (4) { 
-    ["flags"]=> +      ["kind"]=> 
-    int(0) +      int(521) 
-    ["lineno"]=> +      ["flags"]=> 
-    int(0) +      int(0) 
-    ["children"]=> +      ["lineno"]=> 
-    array(2) { +      int(0) 
-      [0]=> +      ["children"]=> 
-      object(ast\Node)#3 (4) { +      array(2) { 
-        ["kind"]=> +        [0]=> 
-        int(520) +        object(ast\Node)#3 (4) { 
-        ["flags"]=> +          ["kind"]=> 
-        int(1) +          int(520) 
-        ["lineno"]=> +          ["flags"]=> 
-        int(0) +          int(1) 
-        ["children"]=> +          ["lineno"]=> 
-        array(2) { +          int(0) 
-          [0]=> +          ["children"]=> 
-          object(ast\Node)#4 (4) { +          array(2) { 
-            ["kind"]=> +            [0]=> 
-            int(256) +            object(ast\Node)#4 (4) { 
-            ["flags"]=> +              ["kind"]=> 
-            int(0) +              int(256) 
-            ["lineno"]=> +              ["flags"]=> 
-            int(0) +              int(0) 
-            ["children"]=> +              ["lineno"]=> 
-            array(1) { +              int(0) 
-              [0]=> +              ["children"]=> 
-              string(1) "a"+              array(1) { 
 +                [0]=> 
 +                string(1) "a" 
 +              }
             }             }
-          } +            [1]=> 
-          [1]=> +            object(ast\Node)#5 (4) { 
-          object(ast\Node)#5 (4) { +              ["kind"]=> 
-            ["kind"]=> +              int(256) 
-            int(256) +              ["flags"]=> 
-            ["flags"]=> +              int(0) 
-            int(0) +              ["lineno"]=> 
-            ["lineno"]=> +              int(0) 
-            int(0) +              ["children"]=> 
-            ["children"]=> +              array(1) { 
-            array(1) { +                [0]=> 
-              [0]=> +                string(1) "b" 
-              string(1) "b"+              }
             }             }
           }           }
         }         }
 +        [1]=>
 +        int(0)
       }       }
-      [1]=> 
-      int(0) 
     }     }
   }   }
 } }
 </code> </code>
 +
 +[[https://github.com/nikic/php-ast|php-ast]] is also going to be included into core PHP distribution, but this is a subject of another RFC.
  
 ==== Use Cases ==== ==== Use Cases ====
  
-With attributes it's extremely simple to mark some functions with some specific attribute and then perform check and special handling in extensions.+With attributesit's extremely simple to mark some functions with some specific "flag" and then perform checks and special handling in extensions.
  
 <code php> <code php>
Line 179: Line 198:
 function foo() { function foo() {
   ...   ...
 +}
 +</code>
 +
 +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->name = $name;
 + }
 + }
 +
 + function GetClassAttributes($class_name) {
 + $reflClass = new \ReflectionClass($class_name);
 + $attrs = $reflClass->getAttributes();
 + foreach ($attrs as $name => &$values) {
 + $name = "Doctrine\\" . $name;
 + $values = new $name(...$values);
 + }
 + return $attrs;
 + }
 +}
 +
 +namespace {
 + <<ORM\Entity("user")>>
 + class User {}
 +
 + var_dump(Doctrine\ORM\GetClassAttributes("User"));
 +}
 +?>
 +</code>
 +
 +<code>
 +array(1) {
 +  ["ORM\Entity"]=>
 +  object(Doctrine\ORM\Entity)#2 (1) {
 +    ["name":"Doctrine\ORM\Entity":private]=>
 +    string(4) "user"
 +  }
 } }
 </code> </code>
Line 204: Line 266:
 </code> </code>
  
-==== Criticism and Alternative Approach ====+==== Special Attributes ====
  
-Today we use single doc-comments for any kind of meta-information, and many people don't see a benefit in introduction of the special syntax. Everything may be grouped together and formatted usng another special language.+<nowiki>Attribute names starting with "__" are reserved for internal purpose. Usage of unknown special attributes leads to compile-time error. Currently, no any special attributes are defined.</nowiki> 
 + 
 +==== Criticism and Alternative Approaches ==== 
 + 
 +=== Doc-comments === 
 + 
 +Today we are using single doc-comments for any kind of meta-information, and many people don't see a benefit in the introduction of special syntax. Everything may be grouped together and formatted using another special language.
  
 <code php> <code php>
Line 241: Line 309:
 </code> </code>
  
-This approach works, but PHP itself dosn't have efficient access to pieces of this information.+This approach works, but PHP itself doesn't have efficient access to pieces of this information.
 e.g. to check "jit" attribute, today, we would perform regular expression matching. e.g. to check "jit" attribute, today, we would perform regular expression matching.
  
 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; doc-comment may not conform to context-grammar and we have to decide what to do with grammar errors; finally this is going to be another language inside PHP. 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; doc-comment may not conform to context-grammar and we have to decide what to do with grammar errors; finally this is going to be another language inside PHP.
 +
 +=== Full Featured Annotation System (like Doctrine) ===
 +
 +This RFC proposes only base PHP attribute functionality. It doesn't define how attributes are validated and used. The full-featured annotation systems may be implemented on top of the base. The following example shows how a real life doc-comment annotation taken from Drupal may be implemented, validated and constructed on top of PHP attributes.
 +
 +<code php>
 +/**
 +  * @Block(
 +  *   id = "system_branding_block",
 +  *   admin_label = @Translation("Site branding")
 +  * )
 +  */
 +
 +<<Drupal(@Block([
 +       "id" => "system_branding_block",
 +       "admin_label" => @Translation("Site branding")
 +]))>>
 +class PageTitleBlock {
 +
 +
 +function TranslateDrupalAttribute($value) {
 +  if ($value instanceof \ast\Node) {
 +    if ($value->kind == 264 && count($value->children) == 1) { // '@'
 +      $a = $value->children[0];
 +      if (is_string($a) && class_exists($a)) {
 +        $value = new $a;
 +      } else if ($a instanceof \ast\Node &&
 +                 $a->kind == 515 && // NAME(ARGS)
 +                 count($a->children) == 2 &&
 +                 is_string($a->children[0]) &&
 +                 class_exists($a->children[0]) &&
 +                 $a->children[1] instanceof \ast\Node &&
 +                 $a->children[1]->kind == 128 &&
 +                 count($a->children[1]->children) == 1) {
 +        $args = $a->children[1]->children[0];
 +        if ($args instanceof ast\Node && $args->kind == 130) {
 +          $obj = new $a->children[0];
 +          foreach ($args->children as $arg) {
 +            if ($arg instanceof ast\Node &&
 +                $arg->kind == 525 &&
 +                count($arg->children) == 2 && 
 +                is_string($arg->children[1])) {
 +              $name = $arg->children[1];
 +              $val = $arg->children[0];
 +              if ($val instanceof ast\Node) {
 +                $obj->{$name} = TranslateDrupalAttribute($val);
 +              } else {
 +                $obj->{$name} = $val;
 +              }
 +            } else {
 +              throw DrupalAnnotationError("...");
 +            }
 +          }
 +        } else {
 +          $name = $a->children[0];
 +          $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->getAttributes();
 +  $ret = [];
 +  foreach ($attrs as $name => $values) {
 +    if ($name == "Drupal") {
 +      foreach ($values as &$value) {
 +        $ret[] = TranslateDrupalAttribute($value);
 +      }
 +    }
 +  }
 +  return $ret;
 +}
 +
 +class Block {}
 +class Translation {
 +  public $text;
 +  function __construct($text) {
 +    $this->text = $text;
 +  }
 +}
 +
 +var_dump(GetDrupalAnnotations("PageTitleBlock"));
 +</code>
 +
 +<code>
 +array(1) {
 +  [0]=>
 +  object(Block)#11 (2) {
 +    ["id"]=>
 +    string(21) "system_branding_block"
 +    ["admin_label"]=>
 +    object(Translation)#12 (1) {
 +      ["text"]=>
 +      string(13) "Site branding"
 +    }
 +  }
 +}
 +</code>
 +
 +=== '@' Prefix in Attribute Names ===
 +
 +'@' symbol may be used in attribute values (as part of PHP expressions) and reused by annotation system for special purpose, but attribute names can't be prefixed with '@' their selves. See the example above.
 +
 +=== Naming (attributes or annotations) ===
 +
 +Different programming languages use different terms for similar features. Some use annotation, some attributes. I prefer name "attributes" because it's used in Hack and makes less fragmentation. It also makes less confusion for external high-level annotation systems (Doctrine, etc).
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
-None+The RFC doesn't make backward incompatibility changes, however, it makes forward incompatibility change. This means that frameworks that use native attributes won't be able to run on PHP versions lower than 7.1.
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
Line 257: Line 440:
  
 ==== To Existing Extensions ==== ==== To Existing Extensions ====
-[[https://github.com/nikic/php-ast|php-ast]] will require minor modification ,because the patch moved classes "\ast\Node" and "\ast\Node\Decl" into core.+[[https://github.com/nikic/php-ast|php-ast]] will require minor modification, because the patch moved classes "\ast\Node" and "\ast\Node\Decl" into core.
  
 ==== To Opcache ==== ==== To Opcache ====
Line 270: Line 453:
 ===== Open Issues ===== ===== Open Issues =====
   * 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.   * 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.
-  * getAttributes() should return empty array in case of no attributes +  * <del>getAttributes() should return empty array in case of no attributes</del> [INCLUDED] 
-  * For each defined attribute getArray() should return a numerically indexed array independently of number of associated values. For attributes without values it should return empty arrays. +  * <del>For each defined attribute getArray() should return a numerically indexed array independently of number of associated values. For attributes without values it should return empty arrays.</del> [INCLUDED] 
-  * Attribute names might be namespace qualified e.g. <nowiki><<\Foo\Bar>></nowiki> +  * <del>Attribute names might be namespace qualified e.g. <nowiki><<\Foo\Bar>></nowiki></del> [INCLUDED] 
-  * It may be useful to optionally allow some extra special character e.g. <nowiki><<@\Foo\Bar>></nowiki>. This character won't have any special meaning for PHP itself, but higher layer may use this "@" as a flag of special meaning. +  * <del>It may be useful to optionally allow some extra special character e.g. <nowiki><<@\Foo\Bar>></nowiki>. This character won't have any special meaning for PHP itself, but higher layer may use this "@" as a flag of special meaning.</del> [ADDED into criticism section] 
-  * May be we don't need special functionality for AST in attributes. We may store attribute as a simple strings and then get them through getAttributes() and call ast\parse_code() to get AST (if necessary). Both enabling and disabling native AST support make sense with their profs and cons.+  * <del>May be we don't need special functionality for AST in attributes. We may store attribute as a simple strings and then get them through getAttributes() and call ast\parse_code() to get AST (if necessary). Both enabling and disabling native AST support make sense with their profs and cons.</del> [ADDITIONAL VOTING QUESTION] 
 +  * <del>Naming: "Attributes" or "Annotation(s)"?</del> [ADDED into criticism section]
      
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
-This RFC modifies the PHP language syntax and therefore requires a two-third majority of votes+The voting started on May 10th, 2016 and will close on May 24th, 2016. 
 + 
 +<doodle title="Accept PHP Attributes? (2/3+1 majority required)" auth="dmitry" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 +---- 
 + 
 +<doodle title="What may be used as attribute value? (simple majority wins)" auth="dmitry" voteType="single" closed="true"> 
 +   * valid PHP expression (internally represented as AST) 
 +   * valid PHP constant (number or string) 
 +</doodle>
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
Line 291: Line 487:
   * [[https://docs.hhvm.com/hack/attributes/introduction|Attributes in Hack]]   * [[https://docs.hhvm.com/hack/attributes/introduction|Attributes in Hack]]
   * [[https://en.wikipedia.org/wiki/Java_annotation|Java Annotation]]   * [[https://en.wikipedia.org/wiki/Java_annotation|Java Annotation]]
 +  * [[https://wiki.php.net/rfc/annotations|Class Metadata RFC]]
 +  * [[https://wiki.php.net/rfc/annotations-in-docblock|Annotations in DocBlock RFC]]
  
-===== Rejected Features ===== 
-Keep this updated with features that were discussed on the mail lists. 
rfc/attributes.1461552669.txt.gz · Last modified: 2017/09/22 13:28 (external edit)