Description
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 for PHP4 #16 - "Metadata for PHP4" (annotations in the XP Framework, 2004 / 2005)
- Change Attribute Syntax from @@ to #[] php/php-src#5989 - Change Attribute Syntax from @@ to #[]
- https://wiki.php.net/rfc/shorter_attribute_syntax_change#syntax_and_bc_breaks1 - BC breaks for
#[...]
- PHP 8 attributes core#243 (comment) - Problem with constant expression restriction
- PHP 8 compatibility core#211 (comment) - Idea with
using: '@values'
- PHP 8 compatibility core#211 (comment) - Idea with new reflection library
- PHP 8 attributes core#243 - Pull request supporting PHP 8 syntax based on reflection
- https://doc.rust-lang.org/reference/attributes.html - Rust uses
#![...]
for inner attributes. We could put the $fixtures inside the functions, and then use that?