Skip to content

Adopt PHP 8 annotation syntax #336

Open
@thekid

Description

@thekid

Scope of Change

With https://wiki.php.net/rfc/shorter_attribute_syntax_change, PHP 8's annotation syntax has become #[Annotation]. XP Framework has been using a very similar syntax for the past 15 years, #[@annotation]. This RFC suggests changing the annotation functionality to support PHP 8 syntax starting with the next feature release so libraries can start adopting.

Rationale

With the introduction of a native annotation syntax in PHP, the XP Framework's annotations have become syntax errors. The above RFC's authors were aware of the fact that this is a BC break but regarded its outcome neglectable. This may seem frustrating, but on the other hand it's nice the new syntax closely resembles what we've been used to!

Functionality

This is how annotations will look like once we adopt PHP's native syntax:

use unittest\{Assert, Test, Expect, Values};
use lang\ElementNotFoundException;

class ListOfTest {

  #[Test]
  public function size_of_empty_list() {
    Assert::equals(0, (new ListOf())->size());
  }

  #[Expect(class: ElementNotFoundException::class), Values([[[], 0], [[1], 1]], [[1, 2, 3], 100]])]
  public function accessing_non_existant($elements, $offset) {
    (new ListOf($elements))->get($offset);
  }
}

Adoption

A multi-step adoption strategy is outlined below:

Step 1: Allow PHP syntax

Starting in the next 10.X feature release, XP Framework will start allowing PHP native syntax alongside the XP Framework's own syntax. PHP attributes #[Test(true)] will be interpreted the same as #[@test(true)].

Step 2: Deprecate XP syntax

With the next major release (11.0), XP Framework will continue to support its own syntax, however raise deprecation warnings. This will keep production code working but will fail unittests.

Step 3: Remove XP syntax

With the major release following that (12.0), XP Framework will remove support for the deprecated XP syntax and only support PHP 8 native annotations (with some additions, see below).

Values

XP Framework offers support for various expressions inside annotation values that PHP native syntax does not support. This is commonly used in the @values annotation inside unittests:

use unittest\Assert;

class ListOfTest {

  #[@test, @values([[new ListOf()], [ListOf::$EMPTY]])]
  public function size_of_empty($list) {
    Assert::equals(0, $list->size());
  }

  #[@test, @values([
  #  [[1, 2, 3]],
  #  [new \ArrayObject([1, 2, 3])],
  #  [function() { yield 1; yield 2; yield 3; }],
  #])]
  public function can_create_from($elements) {
     Assert::equals([1, 2, 3], (new ListOf($elements))->toArray());
  }
}

In PHP, all of these except for the static array [1, 2, 3] yield Compile error (Constant expression contains invalid operations). While considered as potential future benefit in the PHP RFC here, this is currently not implemented. While these can be rewritten to helper methods (see below), this results in code noise and forces authors to think of names for the helper methods, especially when using multiple value-driven tests in a class.

use unittest\{Assert, Test, Values};

class ListOfTest {

  /** @return iterable */
  private function emptyLists() { return [[new ListOf()], [ListOf::$EMPTY]]; }

  #[Test, Values('emptyLists')]
  public function size_of_empty($list) {
    Assert::equals(0, $list->size());
  }
}

One way to overcome this is to use the special named argument eval and evaluate the expression:

use unittest\{Assert, Test, Values};

class ListOfTest {

  #[Test, Values(eval: '[[new ListOf()], [ListOf::$EMPTY]]')]
  public function size_of_empty($list) {
    Assert::equals(0, $list->size());
  }
}

Name resolution

Another difference is that names are resolved in the PHP native syntax, while XP annotations are simply used as-is. So while #[Test] will refer to the type unittest.Test (while not necessarily requiring it to actually exist!) and resolve to "unittest\Test", the old #[@test] simply yields "test".

This would create a strain on library authors, which would have to refactor their code to support both. By continuing to parse PHP 8 attributes from the codebase, this issue is addressed in backwards-compatible way.

Security considerations

n/a

Speed impact

Slightly slower to be able to support both.

Dependencies

PHP 8 for the new native syntax.

Related documents

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions