Table of Contents

PHP RFC: Annotations 2.0

Introduction

Annotation is a form of syntactic metadata that can be added to source code. Annotations can be embedded in and read using reflection mechanism. Annotations are 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 exist 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", true)]
[SimpleAnnotation]
class Foo {}

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 exist at the beginning 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", "uuid")]
    private $id;
}
use MVC\Route;
 
class FooController {
    [@Route("/api/foo", ["POST"], "foo_create")]
    public function create(Request $request): Response
    {
        // specific implementation
    }
}

Built-in annotations

First, there are several that inform compilation:

[Compiled]

Function scoped annotation indicating if a function should be JIT compiled.

[SupressWarnings]

Function or statement scoped annotation indicating if error supression should be applied.

Meta-Annotations

Next, meta-annotations are annotations that can be applied to other annotations.

For example, these meta-annotations are used for annotation configuration:

[@Annotation]

Annotation classes have to contain a @Annotation.

[@Annotation]
class MyAnnotation {
    // some code
}

[@Target]

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

[@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 superclass, they are automatically inherited to subclasses.

[@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

Custom annotations

Annotation type declarations are similar to normal class declarations. Each property declaration defines an element of the annotation type. Property types are restricted to primitives or another annotation type.

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;
    }
}

Using annotations

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

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 the 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 a sort of validation.

Critique

TBD

Backward Incompatible Changes

None.

Proposed PHP Version(s)

Proposed version is next PHP 7.x or PHP 8.

RFC Impact

To SAPIs

None.

To Existing Extensions

None.

To Opcache

Probably 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.

Implementation

TBD.

References

TBD