rfc:attributes_v2

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
Next revisionBoth sides next revision
rfc:attributes_v2 [2020/04/17 11:25] beberleirfc:attributes_v2 [2020/05/04 12:00] – Closed vote beberlei
Line 1: Line 1:
 ====== PHP RFC: Attributes v2 ====== ====== PHP RFC: Attributes v2 ======
  
-  * Version: 0.4+  * Version: 0.5
   * Date: 2020-03-09   * Date: 2020-03-09
   * Author: Benjamin Eberlei (beberlei@php.net), Martin Schröder   * Author: Benjamin Eberlei (beberlei@php.net), Martin Schröder
-  * Status: Under Discussion+  * Status: Voting
   * First Published at: http://wiki.php.net/rfc/attributes_v2   * First Published at: http://wiki.php.net/rfc/attributes_v2
   * Implementation: https://github.com/php/php-src/pull/5394   * Implementation: https://github.com/php/php-src/pull/5394
Line 39: Line 39:
 <nowiki>Attributes are a specially formatted text enclosed with "<<" and ">>" by reusing the existing tokens T_SL and T_SR.</nowiki> <nowiki>Attributes are a specially formatted text enclosed with "<<" and ">>" by reusing the existing tokens T_SL and T_SR.</nowiki>
  
-attributes may be applied to many things in the language ([[https://github.com/php/php-src/pull/5394/files#diff-4ad8674c779b73487e764e83f3589165|see this test for examples]]):+attributes may be applied to many things in the language:
  
   * functions (including closures and short closures)   * functions (including closures and short closures)
Line 47: Line 47:
   * class methods   * class methods
   * function/method parameters   * function/method parameters
 +
 +Examples:
 +
 +<code php>
 +<<ExampleAttribute>>
 +class Foo
 +{
 +    <<ExampleAttribute>>
 +    public const FOO = 'foo';
 +
 +    <<ExampleAttribute>>
 +    public $x;
 +
 +    <<ExampleAttribute>>
 +    public function foo(<<ExampleAttribute>> $bar) { }
 +}
 +
 +$object = new <<ExampleAttribute>> class () { };
 +
 +<<ExampleAttribute>>
 +function f1() { }
 +
 +$f2 = <<ExampleAttribute>> function () { };
 +
 +$f3 = <<ExampleAttribute>> fn () => 1;
 +</code>
  
 Attributes are added before the declaration they belong to, similar to doc-block comments. They can be declared **before** or **after** a doc-block comment that documents a declaration. Attributes are added before the declaration they belong to, similar to doc-block comments. They can be declared **before** or **after** a doc-block comment that documents a declaration.
  
 <code php> <code php>
-<<...>> +<<ExampleAttribute>> 
-<<...>>+/** docblock */ 
 +<<AnotherExampleAttribute>>
 function foo() {} function foo() {}
 </code> </code>
Line 71: Line 98:
 The same attribute name can be used more than once on the same declaration. The same attribute name can be used more than once on the same declaration.
  
-Sementically the attribute declaration should be read as instantiating a class+Attributes can also be declared on the same line: 
 + 
 +<code php> 
 +<<WithoutArgument>><<SingleArgument(0)>><<FewArguments('Hello', 'World')>> 
 +function foo() {} 
 +</code> 
 + 
 +Semantically the attribute declaration should be read as instantiating a class
 with the attribute name and passing arguments to the constructor. with the attribute name and passing arguments to the constructor.
  
Line 212: Line 246:
 **Note:** This is intentionally different from the previous Attributes RFC where an object **Note:** This is intentionally different from the previous Attributes RFC where an object
 with ast\node was returned. with ast\node was returned.
 +
 +The parser understands the context to differentiate attributes from bitshifts in constant ASTs.
 +
 +<code php>
 +<<BitShiftExample(4 >> 1, 4 << 1)>>
 +function foo() {}
 +</code>
  
 ==== Reflection ==== ==== Reflection ====
Line 238: Line 279:
  
 <code php> <code php>
-$attributes = $reflectionFunction->getAttributes(\My\Attributes\MyAbstractAttribute::class, \ReflectionAttribute::IS_INSTANCEOF);+$attributes = $reflectionFunction->getAttributes( 
 +    \My\Attributes\MyAbstractAttribute::class, 
 +    \ReflectionAttribute::IS_INSTANCEOF 
 +);
 </code> </code>
  
Line 248: Line 292:
     public function getName(): string     public function getName(): string
     public function getArguments(): array     public function getArguments(): array
-    public function getAsObject(): object+    public function newInstance(): object
 } }
 </code> </code>
Line 254: Line 298:
  
 Because validation of attributes is only performed during Because validation of attributes is only performed during
-//ReflectionAttribute::getAsObject()//, it is technically not required to+//ReflectionAttribute::newInstance()//, it is technically not required to
 declare the attribute class.  You can still acccess name and arguments directly declare the attribute class.  You can still acccess name and arguments directly
 from //ReflectionAttribute//. from //ReflectionAttribute//.
Line 283: Line 327:
     var_dump($attributes[0]->getName());     var_dump($attributes[0]->getName());
     var_dump($attributes[0]->getArguments());     var_dump($attributes[0]->getArguments());
-    var_dump($attributes[0]->getAsObject());+    var_dump($attributes[0]->newInstance());
 } }
  
Line 486: Line 530:
             foreach ($attributes as $listenerAttribute) {             foreach ($attributes as $listenerAttribute) {
                 /** @var $listener Listener */                 /** @var $listener Listener */
-                $listener = $listenerAttribute->getAsObject();+                $listener = $listenerAttribute->newInstance();
  
                 // with $listener instanceof Listener attribute,                 // with $listener instanceof Listener attribute,
Line 524: Line 568:
  
 Doctrine or any userland library can utilize the name filter with a parent class to fetch Doctrine or any userland library can utilize the name filter with a parent class to fetch
-only attributes they are interested in:+only attributes they are interested in. With the flexibility in the proposed Reflection API, Doctrine (or any other userland 
 +annotation/attributes library) can enforce stricter rules for use of the 
 +attributes by adding their own logic on top wihout PHP attributes getting in 
 +the way. 
 + 
 +Here is a complex example of an object using Doctrine Annotations and the proposed Attributes side by side to implement the same thing:
  
 <code php> <code php>
-namespace Doctrine\Annotations;+<?php 
 +use Doctrine\ORM\Attributes as ORM; 
 +use Symfony\Component\Validator\Constraints as Assert;
  
-abstract class Annotation { +<<ORM\Entity>> 
-    class TARGET_CLASS = 1; +/** @ORM\Entity */ 
-    class TARGET_PROPERTY = 2; +class User
- +
-    public $target = self::TARGET_CLASS; +
- +
-    final public function __construct(array $values = []) { +
-        foreach ($values as $key =$value) { +
-            $this->$key = $value; +
-        } +
-    } +
-} +
- +
-class AnnotationReader+
 { {
-    function getClassAnnotations(\ReflectionClass $reflection): array { +    /** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */ 
-        $doctrineAnnotations = [];+    <<ORM\Id>><<ORM\Column("integer")>><<ORM\GeneratedValue>> 
 +    private $id;
  
-        foreach ($reflection->getAttributes() as $attribute) { +    /** 
-            // filter out any that doesn't extend Doctrine's annotation base class +     * @ORM\Column(type="string", unique=true
-            if (!is_subclass_of($attributeAnnotation::class)) { +     * @Assert\Email(message="The email '{{ value }}is not a valid email.") 
-                continue; +     */ 
-            }+    <<ORM\Column("string"ORM\Column::UNIQUE)>> 
 +    <<Assert\Email(array("message" => "The email '{{ value }}' is not a valid email."))>> 
 +    private $email;
  
-            $annotation $attribute->getAsObject();+    /** 
 +     * @ORM\Column(type="integer"
 +     * @Assert\Range( 
 +          min = 120, 
 +          max = 180, 
 +          minMessage = "You must be at least {{ limit }}cm tall to enter", 
 +          maxMessage = "You cannot be taller than {{ limit }}cm to enter" 
 +     * ) 
 +     */ 
 +    <<Assert\Range(["min" => 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"])>> 
 +    <<ORM\Column(ORM\Column::T_INTEGER)>> 
 +    protected $height;
  
-            // validate that doctrine annotation is on the right "target+    /** 
-            // getClassAnnotations requires all annotations to be allowed on a class +     * @ORM\ManyToMany(targetEntity="Phonenumber") 
-            if (($annotation->target & Annotation::TARGET_CLASS) === 0{ +     * @ORM\JoinTable(name="users_phonenumbers", 
-                throw new \RuntimeException(get_class($annotation) . is not allowed on class.")+          joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 
-            +     *      inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} 
- +     *      ) 
-            $doctrineAnnotations[] = $annotation; +     */ 
-        } +    <<ORM\ManyToMany(Phonenumber::class)>> 
- +    <<ORM\JoinTable("users_phonenumbers")>> 
-        return $doctrineAnnotations; +    <<ORM\JoinColumn("user_id", "id")>> 
-    }+    <<ORM\InverseJoinColumn("phonenumber_id", "id", JoinColumn::UNIQUE)>> 
 +    private $phonenumbers;
 } }
 </code> </code>
  
-With this flexibility in the Reflection API, Doctrine (or any other userland +The attributes approach is a bit limited, because it doesn't support named params. But this is the reason why attributes uses a function call like syntax, if the PHP language makes improvements with named params, then attributes would automatically benefit.
-annotation/attributes library) can also enforce stricter rules for use of the +
-attributes by adding their own logic on top wihout PHP attributes getting in +
-the way.+
  
 [[https://github.com/RectorPHP/Rector|Migration tools such as Rector]] can help with userland migrating to attributes. [[https://github.com/RectorPHP/Rector|Migration tools such as Rector]] can help with userland migrating to attributes.
Line 697: Line 749:
   * Other languages such as Go have simple but powerful serialization from XML/JSON to objects and back. The combination of typed properties an attributes puts this in reach for core or a PHP extension to implement.   * Other languages such as Go have simple but powerful serialization from XML/JSON to objects and back. The combination of typed properties an attributes puts this in reach for core or a PHP extension to implement.
   * An alternative "short" syntax to declare attributes in one enclosing //<<SingleArgument("foo"), MultiArgument("bar", "baz")>>// This could be revisited in the future similar to grouped use statements being added after use statements already existed.   * An alternative "short" syntax to declare attributes in one enclosing //<<SingleArgument("foo"), MultiArgument("bar", "baz")>>// This could be revisited in the future similar to grouped use statements being added after use statements already existed.
-  * Extending userland attributes to allow declaring which target they are allowed to be declared on including validation of those targets in     //ReflectionAttribute::getAsObject()//.+  * Extending userland attributes to allow declaring which target they are allowed to be declared on including validation of those targets in     //ReflectionAttribute::newInstance()//.
  
-===== Proposed Voting Choices =====+===== Voting =====
  
-  * Accept PHP Attributes v2 into core? 2/3 majority +<doodle title="Accept PHP Attributes v2 into core?" auth="beberlei" voteType="single" closed="true"> 
-  * Which syntax to use for attributes? "<<>>" or "@:"+   Yes 
 +   * No 
 +</doodle> 
 + 
 +Secondary vote (choice with the most votes is picked): 
 + 
 +<doodle title=" Which syntax to use for attributes?" auth="beberlei" voteType="single" closed="true"> 
 +   <<>> 
 +   @: 
 +</doodle> 
 + 
 +Vote closes on May 4th, 12:00 UTC.
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
Line 751: Line 814:
   * Changed validation of compiler attributes to use a C callback instead of instantiating object   * Changed validation of compiler attributes to use a C callback instead of instantiating object
   * Offer alternative syntax "@:" using new token T_ATTRIBUTE   * Offer alternative syntax "@:" using new token T_ATTRIBUTE
 +
 +0.5:
 +
 +  * Rename ReflectionAttribute::getAsObject to ReflectionAttribute::newInstance
  
rfc/attributes_v2.txt · Last modified: 2020/08/01 23:38 by carusogabriel