Additionally, care should be taken that error messages are not displayed in
production environments, which can result in information leaks. Please
ensure that display_errors=Off is used in conjunction with error logging.
+ . Adding more than one @ operator to an expression is no longer supported,
+ since this syntax is now used for attributes (previously extra @ operators
+ had no effect).
+ RFC: https://wiki.php.net/rfc/shorter_attribute_syntax
. Inheritance errors due to incompatible method signatures (LSP violations)
will now always generate a fatal error. Previously a warning was generated
in some cases.
. Added support for Attributes
RFC: https://wiki.php.net/rfc/attributes_v2
RFC: https://wiki.php.net/rfc/attribute_amendments
+ RFC: https://wiki.php.net/rfc/shorter_attribute_syntax
. Added support for constructor property promotion (declaring properties in
the constructor signature).
RFC: https://wiki.php.net/rfc/constructor_promotion
--FILE--
<?php
-<<A1(1)>>
+@@A1(1)
class Foo
{
- <<A1(2)>>
+ @@A1(2)
public const FOO = 'foo';
- <<A1(3)>>
+ @@A1(3)
public $x;
- <<A1(4)>>
- public function foo(<<A1(5)>> $a, <<A1(6)>> $b) { }
+ @@A1(4)
+ public function foo(@@A1(5) $a, @@A1(6) $b) { }
}
-$object = new <<A1(7)>> class () { };
+$object = new @@A1(7) class () { };
-<<A1(8)>>
+@@A1(8)
function f1() { }
-$f2 = <<A1(9)>> function () { };
+$f2 = @@A1(9) function () { };
-$f3 = <<A1(10)>> fn () => 1;
+$f3 = @@A1(10) fn () => 1;
$ref = new \ReflectionClass(Foo::class);
namespace My\Attributes {
use Attribute;
- <<Attribute>>
+ @@Attribute
class SingleArgument {
public $argumentValue;
namespace {
use My\Attributes\SingleArgument;
- <<SingleArgument("Hello World")>>
+ @@SingleArgument("Hello World")
class Foo {
}
define('V1', strtoupper(php_sapi_name()));
-<<A1([V1 => V1])>>
+@@A1([V1 => V1])
class C1
{
public const BAR = 'bar';
echo "\n";
-<<A1(V1, 1 + 2, C1::class)>>
+@@A1(V1, 1 + 2, C1::class)
class C2 { }
$ref = new \ReflectionClass(C2::class);
echo "\n";
-<<A1(self::FOO, C1::BAR)>>
+@@A1(self::FOO, C1::BAR)
class C3
{
private const FOO = 'foo';
echo "\n";
-<<ExampleWithShift(4 >> 1)>>
+@@ExampleWithShift(4 >> 1)
class C4 {}
$ref = new \ReflectionClass(C4::class);
var_dump($ref->getAttributes()[0]->getArguments());
echo "\n";
-<<Attribute>>
+@@Attribute
class C5
{
public function __construct() { }
}
-$ref = new \ReflectionFunction(<<C5(MissingClass::SOME_CONST)>> function () { });
+$ref = new \ReflectionFunction(@@C5(MissingClass::SOME_CONST) function () { });
$attr = $ref->getAttributes();
var_dump(count($attr));
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Attributes;
- <<Entity("imported class")>>
- <<ORM\Entity("imported namespace")>>
- <<\Doctrine\ORM\Mapping\Entity("absolute from namespace")>>
- <<\Entity("import absolute from global")>>
- <<Attributes\Table()>>
+ @@Entity("imported class")
+ @@ORM\Entity("imported namespace")
+ @@\Doctrine\ORM\Mapping\Entity("absolute from namespace")
+ @@\Entity("import absolute from global")
+ @@Attributes\Table()
function foo() {
}
}
--FILE--
<?php
-<<Attribute(Attribute::TARGET_FUNCTION)>>
+@@Attribute(Attribute::TARGET_FUNCTION)
class A1
{
public string $name;
}
}
-$ref = new \ReflectionFunction(<<A1('test')>> function () { });
+$ref = new \ReflectionFunction(@@A1('test') function () { });
foreach ($ref->getAttributes() as $attr) {
$obj = $attr->newInstance();
echo "\n";
-$ref = new \ReflectionFunction(<<A1>> function () { });
+$ref = new \ReflectionFunction(@@A1 function () { });
try {
$ref->getAttributes()[0]->newInstance();
echo "\n";
-$ref = new \ReflectionFunction(<<A1([])>> function () { });
+$ref = new \ReflectionFunction(@@A1([]) function () { });
try {
$ref->getAttributes()[0]->newInstance();
echo "\n";
-$ref = new \ReflectionFunction(<<A2>> function () { });
+$ref = new \ReflectionFunction(@@A2 function () { });
try {
$ref->getAttributes()[0]->newInstance();
echo "\n";
-<<Attribute>>
+@@Attribute
class A3
{
private function __construct() { }
}
-$ref = new \ReflectionFunction(<<A3>> function () { });
+$ref = new \ReflectionFunction(@@A3 function () { });
try {
$ref->getAttributes()[0]->newInstance();
echo "\n";
-<<Attribute>>
+@@Attribute
class A4 { }
-$ref = new \ReflectionFunction(<<A4(1)>> function () { });
+$ref = new \ReflectionFunction(@@A4(1) function () { });
try {
$ref->getAttributes()[0]->newInstance();
class A5 { }
-$ref = new \ReflectionFunction(<<A5>> function () { });
+$ref = new \ReflectionFunction(@@A5 function () { });
try {
$ref->getAttributes()[0]->newInstance();
--FILE--
<?php
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
$attr = $ref->getAttributes(A3::class);
var_dump(count($attr));
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
$attr = $ref->getAttributes(A2::class);
var_dump(count($attr), $attr[0]->getName());
-$ref = new \ReflectionFunction(<<A1>> <<A2>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 @@A2 function () { });
$attr = $ref->getAttributes(A2::class);
var_dump(count($attr), $attr[0]->getName(), $attr[1]->getName());
class A2 implements Base { }
class A3 extends A2 { }
-$ref = new \ReflectionFunction(<<A1>> <<A2>> <<A5>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 @@A5 function () { });
$attr = $ref->getAttributes(\stdClass::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump(count($attr));
print_r(array_map(fn ($a) => $a->getName(), $attr));
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
$attr = $ref->getAttributes(A1::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump(count($attr));
print_r(array_map(fn ($a) => $a->getName(), $attr));
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
$attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump(count($attr));
print_r(array_map(fn ($a) => $a->getName(), $attr));
-$ref = new \ReflectionFunction(<<A1>> <<A2>> <<A3>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 @@A3 function () { });
$attr = $ref->getAttributes(A2::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump(count($attr));
print_r(array_map(fn ($a) => $a->getName(), $attr));
-$ref = new \ReflectionFunction(<<A1>> <<A2>> <<A3>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 @@A3 function () { });
$attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF);
var_dump(count($attr));
print_r(array_map(fn ($a) => $a->getName(), $attr));
--FILE--
<?php
-<<Attribute>>
+@@Attribute
function foo() {}
--EXPECTF--
Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s
use Doctrine\ORM\Attributes as ORM;
use Symfony\Component\Validator\Constraints as Assert;
-<<ORM\Entity>>
+@@ORM\Entity
/** @ORM\Entity */
class User
{
/** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */
- <<ORM\Id>><<ORM\Column("integer")>><<ORM\GeneratedValue>>
+ @@ORM\Id
+ @@ORM\Column("integer")
+ @@ORM\GeneratedValue
private $id;
/**
* @ORM\Column(type="string", unique=true)
* @Assert\Email(message="The email '{{ value }}' is not a valid email.")
*/
- <<ORM\Column("string", ORM\Column::UNIQUE)>>
- <<Assert\Email(array("message" => "The email '{{ value }}' is not a valid email."))>>
+ @@ORM\Column("string", ORM\Column::UNIQUE)
+ @@Assert\Email(array("message" => "The email '{{ value }}' is not a valid email."))
private $email;
/**
* maxMessage = "You cannot be taller than {{ limit }}cm to enter"
* )
*/
- <<Assert\Range(["min" => 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"])>>
- <<ORM\Column(ORM\Column::T_INTEGER)>>
+ @@Assert\Range(["min" => 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"])
+ @@ORM\Column(ORM\Column::T_INTEGER)
protected $height;
/**
* inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
- <<ORM\ManyToMany(Phonenumber::class)>>
- <<ORM\JoinTable("users_phonenumbers")>>
- <<ORM\JoinColumn("user_id", "id")>>
- <<ORM\InverseJoinColumn("phonenumber_id", "id", ORM\JoinColumn::UNIQUE)>>
+ @@ORM\ManyToMany(Phonenumber::class)
+ @@ORM\JoinTable("users_phonenumbers")
+ @@ORM\JoinColumn("user_id", "id")
+ @@ORM\InverseJoinColumn("phonenumber_id", "id", ORM\JoinColumn::UNIQUE)
private $phonenumbers;
}
--FILE--
<?php
-<<A1(foo())>>
+@@A1(foo())
class C1 { }
?>
--FILE--
<?php
-<<A2>>
+@@A2
class C1
{
- <<A1>>
+ @@A1
public function foo() { }
}
class C3 extends C1
{
- <<A1>>
+ @@A1
public function bar() { }
}
trait T1
{
- <<A2>>
+ @@A2
public $a;
}
--FILE--
<?php
-assert(0 && ($a = <<A1>><<A2>> function ($a, <<A3(1)>> $b) { }));
+assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) { }));
-assert(0 && ($a = <<A1(1, 2, 1 + 2)>> fn () => 1));
+assert(0 && ($a = @@A1(1, 2, 1 + 2) fn () => 1));
-assert(0 && ($a = new <<A1>> class() {
- <<A1>><<A2>> const FOO = 'foo';
- <<A2>> public $x;
- <<A3>> function a() { }
+assert(0 && ($a = new @@A1 class() {
+ @@A1@@A2 const FOO = 'foo';
+ @@A2 public $x;
+ @@A3 function a() { }
}));
assert(0 && ($a = function () {
- <<A1>> class Test1 { }
- <<A2>> interface Test2 { }
- <<A3>> trait Test3 { }
+ @@A1 class Test1 { }
+ @@A2 interface Test2 { }
+ @@A3 trait Test3 { }
}));
?>
--EXPECTF--
-Warning: assert(): assert(0 && ($a = <<A1>> <<A2>> function ($a, <<A3(1)>> $b) {
+Warning: assert(): assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) {
})) failed in %s on line %d
-Warning: assert(): assert(0 && ($a = <<A1(1, 2, 1 + 2)>> fn() => 1)) failed in %s on line %d
+Warning: assert(): assert(0 && ($a = @@A1(1, 2, 1 + 2) fn() => 1)) failed in %s on line %d
-Warning: assert(): assert(0 && ($a = new <<A1>> class {
- <<A1>>
- <<A2>>
+Warning: assert(): assert(0 && ($a = new @@A1 class {
+ @@A1
+ @@A2
public const FOO = 'foo';
- <<A2>>
+ @@A2
public $x;
- <<A3>>
+ @@A3
public function a() {
}
})) failed in %s on line %d
Warning: assert(): assert(0 && ($a = function () {
- <<A1>>
+ @@A1
class Test1 {
}
- <<A2>>
+ @@A2
interface Test2 {
}
- <<A3>>
+ @@A3
trait Test3 {
}
--FILE--
<?php
-<<A1(self::class, self::FOO)>>
+@@A1(self::class, self::FOO)
class C1
{
- <<A1(self::class, self::FOO)>>
+ @@A1(self::class, self::FOO)
private const FOO = 'foo';
- <<A1(self::class, self::FOO)>>
+ @@A1(self::class, self::FOO)
public $a;
- <<A1(self::class, self::FOO)>>
- public function bar(<<A1(self::class, self::FOO)>> $p) { }
+ @@A1(self::class, self::FOO)
+ public function bar(@@A1(self::class, self::FOO) $p) { }
}
$ref = new \ReflectionClass(C1::class);
trait T1
{
- <<A1(self::class, self::FOO)>>
+ @@A1(self::class, self::FOO)
public function foo() { }
}
public static function foo()
{
- return new <<A1(self::class, self::FOO)>> class() {
+ return new @@A1(self::class, self::FOO) class() {
private const FOO = 'bar';
- <<A1(self::class, self::FOO)>>
+ @@A1(self::class, self::FOO)
public function bar() { }
};
}
class C1
{
- <<A1>>
+ @@A1
public const A = 1, B = 2;
}
class C1
{
- <<A1>>
+ @@A1
public $x, $y;
}
--TEST--
-Attributes: Compiler Attributes can check for target declarations
+Attribute validation callback of internal attributes.
--SKIPIF--
<?php
if (!extension_loaded('zend-test')) {
--FILE--
<?php
-<<ZendTestAttribute>>
+@@ZendTestAttribute
function foo() {
}
--EXPECTF--
-Fatal error: Only classes can be marked with <<ZendTestAttribute>> in %s
+Fatal error: Only classes can be marked with @@ZendTestAttribute in %s
public static function foo()
{
- return <<A1(self::class, self::FOO)>> function (<<A1(self::class, self::FOO)>> $p) { };
+ return @@A1(self::class, self::FOO) function (@@A1(self::class, self::FOO) $p) { };
}
}
--FILE--
<?php
-<<Attr(a->b::c)>>
+@@Attr(a->b::c)
function test() {}
?>
--FILE--
<?php
-<<$x>>
+@@$x
class A {}
?>
--FILE--
<?php
-<<Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)>>
+@@Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)
class A1 { }
-$ref = new \ReflectionFunction(<<A1>> function () { });
+$ref = new \ReflectionFunction(@@A1 function () { });
$attr = $ref->getAttributes()[0];
var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated());
var_dump(get_class($attr->newInstance()));
echo "\n";
-$ref = new \ReflectionObject(new <<A1>> class() { });
+$ref = new \ReflectionObject(new @@A1 class() { });
$attr = $ref->getAttributes()[0];
var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated());
echo "\n";
-$ref = new \ReflectionFunction(<<A1>> <<A1>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A1 function () { });
$attr = $ref->getAttributes()[0];
var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated());
echo "\n";
-<<Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)>>
+@@Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)
class A2 { }
-$ref = new \ReflectionObject(new <<A2>> <<A2>> class() { });
+$ref = new \ReflectionObject(new @@A2 @@A2 class() { });
$attr = $ref->getAttributes()[0];
var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated());
var_dump(get_class($attr->newInstance()));
--FILE--
<?php
-<<Attribute("foo")>>
+@@Attribute("foo")
class A1 { }
?>
--FILE--
<?php
-<<Attribute(-1)>>
+@@Attribute(-1)
class A1 { }
?>
--FILE--
<?php
-<<Attribute(Foo::BAR)>>
+@@Attribute(Foo::BAR)
class A1 { }
?>
--FILE--
<?php
-<<Attribute>>
+@@Attribute
function a1() { }
?>
--FILE--
<?php
-<<Attribute>>
-<<Attribute>>
+@@Attribute
+@@Attribute
class A1 { }
?>
--FILE--
<?php
-<<MyAttribute(...[1, 2, 3])>>
+@@MyAttribute(...[1, 2, 3])
class Foo { }
?>
--FILE--
<?php
-<<MyAttribute(
+@@MyAttribute(
"there",
"are",
"many",
"arguments",
-)>>
+)
class Foo { }
$ref = new \ReflectionClass(Foo::class);
class Test {
public function __construct(
- <<NonNegative>>
+ @@NonNegative
public int $num,
) {}
}
for (i = 0; i < list->children; i++) {
zend_ast *attr = list->child[i];
- smart_str_appends(str, "<<");
+ smart_str_appends(str, "@@");
zend_ast_export_ns_name(str, attr->child[0], 0, indent);
if (attr->child[1]) {
smart_str_appendc(str, ')');
}
- smart_str_appends(str, ">>");
-
if (newlines) {
smart_str_appendc(str, '\n');
zend_ast_export_indent(str, indent);
%token <ident> T_NS_C "'__NAMESPACE__'"
%token END 0 "end of file"
+%token T_ATTRIBUTE "'@@'"
%token T_PLUS_EQUAL "'+='"
%token T_MINUS_EQUAL "'-='"
%token T_MUL_EQUAL "'*='"
;
attribute:
- T_SL attribute_decl T_SR { $$ = $2; }
+ T_ATTRIBUTE attribute_decl { $$ = $2; }
;
attributes:
RETURN_TOKEN_WITH_IDENT(T_RETURN);
}
+<ST_IN_SCRIPTING>"@@" {
+ RETURN_TOKEN(T_ATTRIBUTE);
+}
+
<ST_IN_SCRIPTING>"yield"{WHITESPACE}"from"[^a-zA-Z0-9_\x80-\xff] {
yyless(yyleng - 1);
HANDLE_NEWLINES(yytext, yyleng);
--- /dev/null
+--TEST--
+Attributes are exposed as tokens.
+--FILE--
+<?php
+
+$tokens = token_get_all('<?php @@A1(1, 2) class C1 { }');
+
+$attr = $tokens[1];
+var_dump(token_name(T_ATTRIBUTE));
+var_dump($attr[0] === T_ATTRIBUTE);
+var_dump($attr[1]);
+
+$class = $tokens[2];
+var_dump($class[1]);
+?>
+--EXPECT--
+string(11) "T_ATTRIBUTE"
+bool(true)
+string(2) "@@"
+string(2) "A1"
REGISTER_LONG_CONSTANT("T_METHOD_C", T_METHOD_C, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_FUNC_C", T_FUNC_C, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_NS_C", T_NS_C, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_INC", T_INC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_DEC", T_DEC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_OBJECT_OPERATOR", T_OBJECT_OPERATOR, CONST_CS | CONST_PERSISTENT);
case T_METHOD_C: return "T_METHOD_C";
case T_FUNC_C: return "T_FUNC_C";
case T_NS_C: return "T_NS_C";
+ case T_ATTRIBUTE: return "T_ATTRIBUTE";
case T_INC: return "T_INC";
case T_DEC: return "T_DEC";
case T_OBJECT_OPERATOR: return "T_OBJECT_OPERATOR";
void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
{
if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
- zend_error(E_COMPILE_ERROR, "Only classes can be marked with <<ZendTestAttribute>>");
+ zend_error(E_COMPILE_ERROR, "Only classes can be marked with @@ZendTestAttribute");
}
}