Skip to content

Instantly share code, notes, and snippets.

@brzuchal
Last active September 14, 2018 13:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brzuchal/620c854762c02a94da0ad81b4b29d9e0 to your computer and use it in GitHub Desktop.
Save brzuchal/620c854762c02a94da0ad81b4b29d9e0 to your computer and use it in GitHub Desktop.
Annotations

Introduction

Annotation is a form of syntactic metadata that can be added to source code. Annotations can be embeded in and read using reflection mechanism. Annotations known from Java or so called Attributes in C# and can be retained by VM at run-time and read via reflection. Annotations can be placed in classes, methods, properties and functions.

PHP offers only a single form of such metadata - doc-comments. In userland there exists some annotation reader libraries like Doctrine Annotations which is widely used for eg. to express object-relational mapping metadata.

Proposal

Proposal of this RFC is to provide annotations with @ prefix in the place the're declared like:

@AnnotationName("value", namedParamter=true, embeded=@EmbededAnnotation)

Which can be used to annotate classes, properties, methods and functions. They look similar to new object instantiation but there is a wide range of inhibitions. Annotations can have named and/or unnamed parameters, they're name can be imported using use statement same way as class names but the place they're declared must use @ prefix. The same as instantiating new object parenthesis is not obligatory if there are no parameters to pass. Unnamed parameters must exists at the begining of parameters list and they must meet constructor parameter requirements.

use ORM\Entity;
use ORM\Id;
use ORM\Column;

@Entity
@Table("foo")
class Foo {
    @Id @Column("id", type="uuid")
    private $id;
}
use MVC\Route;

class FooController {
    @Route("/api/foo", methods=["POST"], name="foo_create")
    public function create(Request $request): Response
    {
        // specific implementation
    }
}

Built in annotations

@Annotation

Annotation classes have to contain a @Annotation.

@Annotation
class MyAnnotation {
    // some code
}

@Target

@Target annotation indicates the kinds of class element or function which an annotation type is applicable. Then you could define one or more targets:

  • CLASS allowed before class declaration
  • PROPERTY allowed before class property declaration
  • METHOD allowed before class method declaration
  • FUNCTION allowed before function declaration
  • ALL allowed in all cases, also default value
  • ANNOTATION allowed before annotation class declaration

@Repeatable

@Repeatable annotation indicates the annotation may be repeated multiple times when annotating.

@Inherited

@Inherited annotation can be used as meta-annotation on the other user defined annotation classes. When such user defined annotations are used on super class, they are automatically inherited to sub classes.

@Annotation
class MyAnnotation {}

@Annotation
@Inherited
class MyInheritedAnnotation {}

@MyAnnotation
@MyInheritedAnnotation
class Foo {}

class Bar extends Foo {}

$refl = new ReflectionClass(Bar::class);
$classAnnotations = $refl->getAnnotations(); // will include @MyInheritedAnnotation only

Examples

Custom annotations

Using annotations

use Example\MyAnnotation;
use Example\MyEmbededAnnotation;

@MyAnnotation(
    myProperty="value", 
    myArrayProperty=[1, 3.14, true, "string", DIRECTORY_SEPARATOR], 
    myEmbeded=@MyEmbededAnnotation()
)
class Foo {
    @MyPropertyAnnotation
    private $property;
  
    @MyMethodAnnotation("value")
    public function bar() {}
}

Declaring custom annotations

namespace Example;

@Annotation
@Target("class")
class MyAnnotation {
    @Required
    public string $myProperty;
    public array $myArrayProperty = [];
    public MyEmbededAnnotation $myEmbeded;
}

@Annotation
@Target(["class", "annotation"])
class MyEmbededAnnotation {
}

@Annotation
@Target("property")
class MyPropertyAnnotation {
}

@Annotation
@Target("method")
class MyMethodAnnotation {
    public string $value;
    public function __construct(string $value) {
        $this->value = $value;
    }
}

Reading annotations

$refl = new ReflectionClass(Foo::class);
$classAnnotations = $refl->getAnnotations();
$propertyAnnotations = $refl->getProperty('property')->getAnnotations();
$methodAnnotations = $refl->getMethod('foo')->getAnnotations();

Benefits

Caching

Annotations are cached with source code in OPCache. Which is different than userland implementations which are stored cached outside of source code. This means they don't need invoking reread after cache invalidation.

Live with the code

Provided annotations live with the code so it's easy to enable/disable behaviour of components which use their metadata.

For eg. @Route in MVC style application is metadata for controller method which is used by routing component. Which means any changes to the controller implementation or it's routing metadata lives in one place and therefore can be for eg. commented out with the controller method.

IDE support

Provided annotations can be verified in IDE. The IDE can also provide auto-completion support or sort of validation.

Critique

Reflection

Backward Incompatible Changes

None.

Proposed PHP Version

PHP 7.4

RFC Impact

To SAPIs

None.

To Existing Extensions

None.

To OPCache

Yes.

Proposed Voting Choices

As this is a language change, a 2/3 majority is required. The vote is a straight Yes/No vote for accepting the RFC and merging the patch.

Patches and Tests

TBD.

Discussion

TBD.

@brzuchal
Copy link
Author

Missing parts:

  • parameter allowed types
  • instantiating - when by property and when by constructor
  • enumerated values
  • prohibiting standalone instantiation
  • functions?
  • opcache

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment