rfc:attribute_amendments

PHP RFC: Attribute Amendments

Introduction

This RFC discusses a few amendments to the original Attributes RFC that was accepted for PHP 8.

Rename PhpAttribute class to Attribute

Important Context: Since the acceptence of Attributes, the PhpCompilerAttribute and PhpAttribute classes were unified to be only PhpAttribute. This was needed because the previous approach of disallowing the use of <<PhpCompilerAttribute>> in userland would break "stub" generation that static analysis and IDEs rely on to fill their type database of internal functions/classes. Without it there was no functionality left in PhpCompilerAttribute to preserve.

The original RFC introduced a PhpAttribute class to be added to new userland attributes. This name was inspired by PhpToken and to potentially avoid breaking existing userland codebases with a class called Attribute. However the Php prefix makes no sense for the attribute class compared to PhpToken.

In absence of a namespace policy, the global namespace is PHPs namespace. The documentation states as much and the vote on https://wiki.php.net/rfc/php-namespace-in-core confirmed this.

Therefore we propose to rename PhpAttribute to be just Attribute and in addition recommend that all internal/compiler attributes should be placed under the global namespace as well.

Extensions providing non-core attributes should consider using their own namespace, but this RFC makes no recommendation or rule about this.

Group statement for Attributes

Right now each attribute requires to be surrounded by the T_SL/T_SR tokens:

<<Attr1>><<Attr2>>
class Example
{
    <<Attr2("foo")>>
    <<Attr2("bar")>>
    public function test()
    {
    }
}

We propose to allow a second syntax/style to group attributes in one declaration:

<<Attr1, Attr2>>
class Example
{
    <<Attr2("foo"),
      Attr2("bar")>>
    public function test()
    {
    }
}

In line with many other recent RFCs, trailing commas will also be possible in an attribute group declarations:

    <<
      Attr1("foo"),
      Attr2("bar"),
    >>
    public function test()
    {
    }

Validate Attribute Target Declarations

At the moment an attribute can be put on any kind of declaration and the developer of the attribute has no way of restricting the usage to only a subset of declarations. This functionality is only available to internal attributes for now.

As an example, a developer might introduce an attribute to declare what database table a class is stored in:

use ORM\Attributes\Table;
 
<<Table("users")>>
class User
{
}

But they cannot prevent the Table attribute being used on another declaration where it semantically does not belong:

use ORM\Attributes\Table;
 
class User
{
    <<Table("users")>>
    public $id;
}

We propose that you can optionally restrict the declarations an attribute can be used on by setting an optional argument to PhpAttribute (or Attribute).

namespace ORM\Attributes;
 
<<PhpAttribute(PhpAttribute::TARGET_CLASS)>>
class Table
{
}

The following constants will be added to PhpAttribute:

class PhpAttribute
{
    public const int TARGET_CLASS = 1;
    public const int TARGET_FUNCTION = (1 << 1);
    public const int TARGET_METHOD = (1 << 2);
    public const int TARGET_PROPERTY = (1 << 3);
    public const int TARGET_CLASS_CONSTANT = (1 << 4);
    public const int TARGET_PARAMETER = (1 << 5);
    public const int TARGET_ALL = ((1 << 6) - 1);
 
    public function __construct(int $target = self::TARGET_ALL)
    {
    }
}

Important note: The target definition of an attribute is validated during the call to ReflectionAttribute::newInstance. In fact it does not influence a call to Reflection*::getAttributes() and ReflectionAttribute instances can be returned from this method, that are not valid on the reflected declaration. This is in line with the deferred validation of userland attributes that the original RFC championed.

Validate Attribute Repeatability

At the moment every attribute can be repeated multiple times on a declaration. For many use cases of attributes this is not desired.

For this reason we propose that by default attributes are not repeatable, and only if the attribute has the RepeatableAttribute attribute in addition to PhpAttribute should it be possible to use it multiple times on the same declaration:

<<PhpAttribute, RepeatableAttribute>>
class Route
{
}
 
class HomepageController
{
    <<Route("/")>>
    <<Route("/homepage")>>
    public function indexAction()
    {
    }
}

An alternative approach would be to introduce a second argument to PhpAttribute for flags:

<<PhpAttribute(PhpAttribute::TARGET_ALL, PhpAttribute::IS_REPEATABLE)>>
class Route
{
}
 
// with named parameters:
<<PhpAttribute(flags: PhpAttribute::IS_REPEATABLE)>>
class Route
{
}

Important note: The repeatable definition of an attribute is validated during the call to ReflectionAttribute::newInstance. In fact it does not influence a call to Reflection*::getAttributes() and ReflectionAttribute instances can be returned from this method, that are not valid on the reflected declaration. This is in line with the deferred validation of userland attributes that the original RFC championed.

Backward Incompatible Changes

Introducing a class Attribute into the global namespace is certainly going to break at least a handful of applications using this class name.

Proposed PHP Version(s)

8.0

RFC Impact

To SAPIs

none

To Existing Extensions

none

To Opcache

none

Open Issues

  1. For repeated attribute declaration: Use a dedicated attribute class or flags on PhpAttribute?

Proposed Voting Choices

  1. Should PhpAttribute be renamed to Attribute?
  2. Should a secondary grouped syntax for attributes be introduced?
  3. Should attributes allow definition of target declarations?
  4. Should attributes allow definition of repeatability?

Patches and Tests

References

rfc/attribute_amendments.txt · Last modified: 2020/06/04 09:11 by beberlei