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
rfc:attributes_v2 [2020/04/17 10:45] – Add a sentence with missing differences between syntaxes. beberleirfc:attributes_v2 [2020/08/01 23:38] (current) carusogabriel
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: Implemented 
 +  * Target: 8.0
   * 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 47: Line 48:
   * 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 99:
 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 247:
 **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 280:
  
 <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 293:
     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 299:
  
 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 328:
     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 531:
             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 569:
  
 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 750:
   * 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 815:
   * 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.1587120328.txt.gz · Last modified: 2020/04/17 10:45 by beberlei