summaryrefslogtreecommitdiff
path: root/Zend/tests/attributes
diff options
context:
space:
mode:
authorBenjamin Eberlei <kontakt@beberlei.de>2020-05-24 20:57:00 +0200
committerBenjamin Eberlei <kontakt@beberlei.de>2020-06-04 18:19:49 +0200
commita7908c2d11439a203be92e765b1e000498689e00 (patch)
tree134edc814397a6ffa5218bf5b2153983c09b152e /Zend/tests/attributes
parent970ac28e274b7edcd46bad4588d40d444d4f9074 (diff)
downloadphp-git-a7908c2d11439a203be92e765b1e000498689e00.tar.gz
Add Attributes
Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
Diffstat (limited to 'Zend/tests/attributes')
-rw-r--r--Zend/tests/attributes/001_placement.phpt134
-rw-r--r--Zend/tests/attributes/002_rfcexample.phpt43
-rw-r--r--Zend/tests/attributes/003_ast_nodes.phpt109
-rw-r--r--Zend/tests/attributes/004_name_resolution.phpt93
-rw-r--r--Zend/tests/attributes/005_objects.phpt120
-rw-r--r--Zend/tests/attributes/006_filter.phpt112
-rw-r--r--Zend/tests/attributes/007_self_reflect_attribute.phpt19
-rw-r--r--Zend/tests/attributes/008_wrong_attribution.phpt9
-rw-r--r--Zend/tests/attributes/009_doctrine_annotations_example.phpt159
-rw-r--r--Zend/tests/attributes/010_unsupported_const_expression.phpt11
-rw-r--r--Zend/tests/attributes/011-inheritance.phpt99
-rw-r--r--Zend/tests/attributes/012-ast-export.phpt54
-rw-r--r--Zend/tests/attributes/013_class_scope.phpt117
-rw-r--r--Zend/tests/attributes/014_class_const_group.phpt14
-rw-r--r--Zend/tests/attributes/015_property_group.phpt14
-rw-r--r--Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt15
-rw-r--r--Zend/tests/attributes/017_closure_scope.phpt53
17 files changed, 1175 insertions, 0 deletions
diff --git a/Zend/tests/attributes/001_placement.phpt b/Zend/tests/attributes/001_placement.phpt
new file mode 100644
index 0000000000..cf7bcd4504
--- /dev/null
+++ b/Zend/tests/attributes/001_placement.phpt
@@ -0,0 +1,134 @@
+--TEST--
+Attributes can be placed on all supported elements.
+--FILE--
+<?php
+
+<<A1(1)>>
+class Foo
+{
+ <<A1(2)>>
+ public const FOO = 'foo';
+
+ <<A1(3)>>
+ public $x;
+
+ <<A1(4)>>
+ public function foo(<<A1(5)>> $a, <<A1(6)>> $b) { }
+}
+
+$object = new <<A1(7)>> class () { };
+
+<<A1(8)>>
+function f1() { }
+
+$f2 = <<A1(9)>> function () { };
+
+$f3 = <<A1(10)>> fn () => 1;
+
+$ref = new \ReflectionClass(Foo::class);
+
+$sources = [
+ $ref,
+ $ref->getReflectionConstant('FOO'),
+ $ref->getProperty('x'),
+ $ref->getMethod('foo'),
+ $ref->getMethod('foo')->getParameters()[0],
+ $ref->getMethod('foo')->getParameters()[1],
+ new \ReflectionObject($object),
+ new \ReflectionFunction('f1'),
+ new \ReflectionFunction($f2),
+ new \ReflectionFunction($f3)
+];
+
+foreach ($sources as $r) {
+ $attr = $r->getAttributes();
+ var_dump(get_class($r), count($attr));
+
+ foreach ($attr as $a) {
+ var_dump($a->getName(), $a->getArguments());
+ }
+
+ echo "\n";
+}
+
+?>
+--EXPECT--
+string(15) "ReflectionClass"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(1)
+}
+
+string(23) "ReflectionClassConstant"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(2)
+}
+
+string(18) "ReflectionProperty"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(3)
+}
+
+string(16) "ReflectionMethod"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(4)
+}
+
+string(19) "ReflectionParameter"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(5)
+}
+
+string(19) "ReflectionParameter"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(6)
+}
+
+string(16) "ReflectionObject"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(7)
+}
+
+string(18) "ReflectionFunction"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(8)
+}
+
+string(18) "ReflectionFunction"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(9)
+}
+
+string(18) "ReflectionFunction"
+int(1)
+string(2) "A1"
+array(1) {
+ [0]=>
+ int(10)
+}
diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt
new file mode 100644
index 0000000000..0d3879877e
--- /dev/null
+++ b/Zend/tests/attributes/002_rfcexample.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Attributes: Example from Attributes RFC
+--FILE--
+<?php
+// https://wiki.php.net/rfc/attributes_v2#attribute_syntax
+namespace My\Attributes {
+ use PhpAttribute;
+
+ <<PhpAttribute>>
+ class SingleArgument {
+ public $argumentValue;
+
+ public function __construct($argumentValue) {
+ $this->argumentValue = $argumentValue;
+ }
+ }
+}
+
+namespace {
+ use My\Attributes\SingleArgument;
+
+ <<SingleArgument("Hello World")>>
+ class Foo {
+ }
+
+ $reflectionClass = new \ReflectionClass(Foo::class);
+ $attributes = $reflectionClass->getAttributes();
+
+ var_dump($attributes[0]->getName());
+ var_dump($attributes[0]->getArguments());
+ var_dump($attributes[0]->newInstance());
+}
+?>
+--EXPECTF--
+string(28) "My\Attributes\SingleArgument"
+array(1) {
+ [0]=>
+ string(11) "Hello World"
+}
+object(My\Attributes\SingleArgument)#3 (1) {
+ ["argumentValue"]=>
+ string(11) "Hello World"
+}
diff --git a/Zend/tests/attributes/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt
new file mode 100644
index 0000000000..cf43e663d5
--- /dev/null
+++ b/Zend/tests/attributes/003_ast_nodes.phpt
@@ -0,0 +1,109 @@
+--TEST--
+Attributes can deal with AST nodes.
+--FILE--
+<?php
+
+define('V1', strtoupper(php_sapi_name()));
+
+<<A1([V1 => V1])>>
+class C1
+{
+ public const BAR = 'bar';
+}
+
+$ref = new \ReflectionClass(C1::class);
+$attr = $ref->getAttributes();
+var_dump(count($attr));
+
+$args = $attr[0]->getArguments();
+var_dump(count($args), $args[0][V1] === V1);
+
+echo "\n";
+
+<<A1(V1, 1 + 2, C1::class)>>
+class C2 { }
+
+$ref = new \ReflectionClass(C2::class);
+$attr = $ref->getAttributes();
+var_dump(count($attr));
+
+$args = $attr[0]->getArguments();
+var_dump(count($args));
+var_dump($args[0] === V1);
+var_dump($args[1] === 3);
+var_dump($args[2] === C1::class);
+
+echo "\n";
+
+<<A1(self::FOO, C1::BAR)>>
+class C3
+{
+ private const FOO = 'foo';
+}
+
+$ref = new \ReflectionClass(C3::class);
+$attr = $ref->getAttributes();
+var_dump(count($attr));
+
+$args = $attr[0]->getArguments();
+var_dump(count($args));
+var_dump($args[0] === 'foo');
+var_dump($args[1] === C1::BAR);
+
+echo "\n";
+
+<<ExampleWithShift(4 >> 1)>>
+class C4 {}
+$ref = new \ReflectionClass(C4::class);
+var_dump($ref->getAttributes()[0]->getArguments());
+
+echo "\n";
+
+<<PhpAttribute>>
+class C5
+{
+ public function __construct() { }
+}
+
+$ref = new \ReflectionFunction(<<C5(MissingClass::SOME_CONST)>> function () { });
+$attr = $ref->getAttributes();
+var_dump(count($attr));
+
+try {
+ $attr[0]->getArguments();
+} catch (\Error $e) {
+ var_dump($e->getMessage());
+}
+
+try {
+ $attr[0]->newInstance();
+} catch (\Error $e) {
+ var_dump($e->getMessage());
+}
+
+?>
+--EXPECT--
+int(1)
+int(1)
+bool(true)
+
+int(1)
+int(3)
+bool(true)
+bool(true)
+bool(true)
+
+int(1)
+int(2)
+bool(true)
+bool(true)
+
+array(1) {
+ [0]=>
+ int(2)
+}
+
+int(1)
+string(30) "Class 'MissingClass' not found"
+string(30) "Class 'MissingClass' not found"
+
diff --git a/Zend/tests/attributes/004_name_resolution.phpt b/Zend/tests/attributes/004_name_resolution.phpt
new file mode 100644
index 0000000000..1ee1f5f575
--- /dev/null
+++ b/Zend/tests/attributes/004_name_resolution.phpt
@@ -0,0 +1,93 @@
+--TEST--
+Resolve attribute names
+--FILE--
+<?php
+function dump_attributes($attributes) {
+ $arr = [];
+ foreach ($attributes as $attribute) {
+ $arr[] = ['name' => $attribute->getName(), 'args' => $attribute->getArguments()];
+ }
+ var_dump($arr);
+}
+
+namespace Doctrine\ORM\Mapping {
+ class Entity {
+ }
+}
+
+namespace Doctrine\ORM\Attributes {
+ class Table {
+ }
+}
+
+namespace Foo {
+ use Doctrine\ORM\Mapping\Entity;
+ 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()>>
+ function foo() {
+ }
+}
+
+namespace {
+ class Entity {}
+
+ dump_attributes((new ReflectionFunction('Foo\foo'))->getAttributes());
+}
+?>
+--EXPECTF--
+array(5) {
+ [0]=>
+ array(2) {
+ ["name"]=>
+ string(27) "Doctrine\ORM\Mapping\Entity"
+ ["args"]=>
+ array(1) {
+ [0]=>
+ string(14) "imported class"
+ }
+ }
+ [1]=>
+ array(2) {
+ ["name"]=>
+ string(27) "Doctrine\ORM\Mapping\Entity"
+ ["args"]=>
+ array(1) {
+ [0]=>
+ string(18) "imported namespace"
+ }
+ }
+ [2]=>
+ array(2) {
+ ["name"]=>
+ string(27) "Doctrine\ORM\Mapping\Entity"
+ ["args"]=>
+ array(1) {
+ [0]=>
+ string(23) "absolute from namespace"
+ }
+ }
+ [3]=>
+ array(2) {
+ ["name"]=>
+ string(6) "Entity"
+ ["args"]=>
+ array(1) {
+ [0]=>
+ string(27) "import absolute from global"
+ }
+ }
+ [4]=>
+ array(2) {
+ ["name"]=>
+ string(29) "Doctrine\ORM\Attributes\Table"
+ ["args"]=>
+ array(0) {
+ }
+ }
+}
diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt
new file mode 100644
index 0000000000..baf51af775
--- /dev/null
+++ b/Zend/tests/attributes/005_objects.phpt
@@ -0,0 +1,120 @@
+--TEST--
+Attributes can be converted into objects.
+--FILE--
+<?php
+
+<<PhpAttribute>>
+class A1
+{
+ public string $name;
+ public int $ttl;
+
+ public function __construct(string $name, int $ttl = 50)
+ {
+ $this->name = $name;
+ $this->ttl = $ttl;
+ }
+}
+
+$ref = new \ReflectionFunction(<<A1('test')>> function () { });
+
+foreach ($ref->getAttributes() as $attr) {
+ $obj = $attr->newInstance();
+
+ var_dump(get_class($obj), $obj->name, $obj->ttl);
+}
+
+echo "\n";
+
+$ref = new \ReflectionFunction(<<A1>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\ArgumentCountError $e) {
+ var_dump('ERROR 1', $e->getMessage());
+}
+
+echo "\n";
+
+$ref = new \ReflectionFunction(<<A1([])>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\TypeError $e) {
+ var_dump('ERROR 2', $e->getMessage());
+}
+
+echo "\n";
+
+$ref = new \ReflectionFunction(<<A2>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\Error $e) {
+ var_dump('ERROR 3', $e->getMessage());
+}
+
+echo "\n";
+
+<<PhpAttribute>>
+class A3
+{
+ private function __construct() { }
+}
+
+$ref = new \ReflectionFunction(<<A3>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\Error $e) {
+ var_dump('ERROR 4', $e->getMessage());
+}
+
+echo "\n";
+
+<<PhpAttribute>>
+class A4 { }
+
+$ref = new \ReflectionFunction(<<A4(1)>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\Error $e) {
+ var_dump('ERROR 5', $e->getMessage());
+}
+
+echo "\n";
+
+class A5 { }
+
+$ref = new \ReflectionFunction(<<A5>> function () { });
+
+try {
+ $ref->getAttributes()[0]->newInstance();
+} catch (\Error $e) {
+ var_dump('ERROR 6', $e->getMessage());
+}
+
+?>
+--EXPECT--
+string(2) "A1"
+string(4) "test"
+int(50)
+
+string(7) "ERROR 1"
+string(81) "Too few arguments to function A1::__construct(), 0 passed and at least 1 expected"
+
+string(7) "ERROR 2"
+string(74) "A1::__construct(): Argument #1 ($name) must be of type string, array given"
+
+string(7) "ERROR 3"
+string(30) "Attribute class 'A2' not found"
+
+string(7) "ERROR 4"
+string(50) "Attribute constructor of class 'A3' must be public"
+
+string(7) "ERROR 5"
+string(71) "Attribute class 'A4' does not have a constructor, cannot pass arguments"
+
+string(7) "ERROR 6"
+string(78) "Attempting to use class 'A5' as attribute that does not have <<PhpAttribute>>."
diff --git a/Zend/tests/attributes/006_filter.phpt b/Zend/tests/attributes/006_filter.phpt
new file mode 100644
index 0000000000..2b7cc9dcdb
--- /dev/null
+++ b/Zend/tests/attributes/006_filter.phpt
@@ -0,0 +1,112 @@
+--TEST--
+Attributes can be filtered by name and base type.
+--FILE--
+<?php
+
+$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$attr = $ref->getAttributes(A3::class);
+
+var_dump(count($attr));
+
+$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 () { });
+$attr = $ref->getAttributes(A2::class);
+
+var_dump(count($attr), $attr[0]->getName(), $attr[1]->getName());
+
+echo "\n";
+
+interface Base { }
+class A1 implements Base { }
+class A2 implements Base { }
+class A3 extends A2 { }
+
+$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 () { });
+$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 () { });
+$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 () { });
+$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 () { });
+$attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF);
+var_dump(count($attr));
+print_r(array_map(fn ($a) => $a->getName(), $attr));
+
+echo "\n";
+
+$ref = new \ReflectionFunction(function () { });
+
+try {
+ $ref->getAttributes(A1::class, 3);
+} catch (\Error $e) {
+ var_dump('ERROR 1', $e->getMessage());
+}
+
+$ref = new \ReflectionFunction(function () { });
+
+try {
+ $ref->getAttributes(SomeMissingClass::class, \ReflectionAttribute::IS_INSTANCEOF);
+} catch (\Error $e) {
+ var_dump('ERROR 2', $e->getMessage());
+}
+
+?>
+--EXPECT--
+int(0)
+int(1)
+string(2) "A2"
+int(2)
+string(2) "A2"
+string(2) "A2"
+
+int(0)
+Array
+(
+)
+int(1)
+Array
+(
+ [0] => A1
+)
+int(2)
+Array
+(
+ [0] => A1
+ [1] => A2
+)
+int(2)
+Array
+(
+ [0] => A2
+ [1] => A3
+)
+int(3)
+Array
+(
+ [0] => A1
+ [1] => A2
+ [2] => A3
+)
+
+string(7) "ERROR 1"
+string(103) "ReflectionFunctionAbstract::getAttributes(): Argument #2 ($flags) must be a valid attribute filter flag"
+string(7) "ERROR 2"
+string(34) "Class 'SomeMissingClass' not found"
diff --git a/Zend/tests/attributes/007_self_reflect_attribute.phpt b/Zend/tests/attributes/007_self_reflect_attribute.phpt
new file mode 100644
index 0000000000..ae19665dcb
--- /dev/null
+++ b/Zend/tests/attributes/007_self_reflect_attribute.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Attributes: attributes on PhpAttribute return itself
+--FILE--
+<?php
+
+$reflection = new \ReflectionClass(PhpAttribute::class);
+$attributes = $reflection->getAttributes();
+
+foreach ($attributes as $attribute) {
+ var_dump($attribute->getName());
+ var_dump($attribute->getArguments());
+ var_dump($attribute->newInstance());
+}
+--EXPECTF--
+string(12) "PhpAttribute"
+array(0) {
+}
+object(PhpAttribute)#3 (0) {
+}
diff --git a/Zend/tests/attributes/008_wrong_attribution.phpt b/Zend/tests/attributes/008_wrong_attribution.phpt
new file mode 100644
index 0000000000..dcb0b6b51d
--- /dev/null
+++ b/Zend/tests/attributes/008_wrong_attribution.phpt
@@ -0,0 +1,9 @@
+--TEST--
+Attributes: Prevent PhpAttribute on non classes
+--FILE--
+<?php
+
+<<PhpAttribute>>
+function foo() {}
+--EXPECTF--
+Fatal error: Only classes can be marked with <<PhpAttribute>> in %s
diff --git a/Zend/tests/attributes/009_doctrine_annotations_example.phpt b/Zend/tests/attributes/009_doctrine_annotations_example.phpt
new file mode 100644
index 0000000000..38fc6389e0
--- /dev/null
+++ b/Zend/tests/attributes/009_doctrine_annotations_example.phpt
@@ -0,0 +1,159 @@
+--TEST--
+Doctrine-like attributes usage
+--FILE--
+<?php
+
+namespace Doctrine\ORM\Attributes {
+ class Annotation { public $values; public function construct() { $this->values = func_get_args(); } }
+ class Entity extends Annotation {}
+ class Id extends Annotation {}
+ class Column extends Annotation { const UNIQUE = 'unique'; const T_INTEGER = 'integer'; }
+ class GeneratedValue extends Annotation {}
+ class JoinTable extends Annotation {}
+ class ManyToMany extends Annotation {}
+ class JoinColumn extends Annotation { const UNIQUE = 'unique'; }
+ class InverseJoinColumn extends Annotation {}
+}
+
+namespace Symfony\Component\Validator\Constraints {
+ class Annotation { public $values; public function construct() { $this->values = func_get_args(); } }
+ class Email extends Annotation {}
+ class Range extends Annotation {}
+}
+
+namespace {
+use Doctrine\ORM\Attributes as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+<<ORM\Entity>>
+/** @ORM\Entity */
+class User
+{
+ /** @ORM\Id @ORM\Column(type="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."))>>
+ private $email;
+
+ /**
+ * @ORM\Column(type="integer")
+ * @Assert\Range(
+ * min = 120,
+ * max = 180,
+ * minMessage = "You must be at least {{ limit }}cm tall to enter",
+ * 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)>>
+ protected $height;
+
+ /**
+ * @ORM\ManyToMany(targetEntity="Phonenumber")
+ * @ORM\JoinTable(name="users_phonenumbers",
+ * joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
+ * 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)>>
+ private $phonenumbers;
+}
+
+$class = new ReflectionClass(User::class);
+$attributes = $class->getAttributes();
+
+foreach ($attributes as $attribute) {
+ var_dump($attribute->getName(), $attribute->getArguments());
+}
+
+foreach ($class->getProperties() as $property) {
+ $attributes = $property->getAttributes();
+
+ foreach ($attributes as $attribute) {
+ var_dump($attribute->getName(), $attribute->getArguments());
+ }
+}
+}
+?>
+--EXPECT--
+string(30) "Doctrine\ORM\Attributes\Entity"
+array(0) {
+}
+string(26) "Doctrine\ORM\Attributes\Id"
+array(0) {
+}
+string(30) "Doctrine\ORM\Attributes\Column"
+array(1) {
+ [0]=>
+ string(7) "integer"
+}
+string(38) "Doctrine\ORM\Attributes\GeneratedValue"
+array(0) {
+}
+string(30) "Doctrine\ORM\Attributes\Column"
+array(2) {
+ [0]=>
+ string(6) "string"
+ [1]=>
+ string(6) "unique"
+}
+string(45) "Symfony\Component\Validator\Constraints\Email"
+array(1) {
+ [0]=>
+ array(1) {
+ ["message"]=>
+ string(45) "The email '{{ value }}' is not a valid email."
+ }
+}
+string(45) "Symfony\Component\Validator\Constraints\Range"
+array(1) {
+ [0]=>
+ array(3) {
+ ["min"]=>
+ int(120)
+ ["max"]=>
+ int(180)
+ ["minMessage"]=>
+ string(48) "You must be at least {{ limit }}cm tall to enter"
+ }
+}
+string(30) "Doctrine\ORM\Attributes\Column"
+array(1) {
+ [0]=>
+ string(7) "integer"
+}
+string(34) "Doctrine\ORM\Attributes\ManyToMany"
+array(1) {
+ [0]=>
+ string(11) "Phonenumber"
+}
+string(33) "Doctrine\ORM\Attributes\JoinTable"
+array(1) {
+ [0]=>
+ string(18) "users_phonenumbers"
+}
+string(34) "Doctrine\ORM\Attributes\JoinColumn"
+array(2) {
+ [0]=>
+ string(7) "user_id"
+ [1]=>
+ string(2) "id"
+}
+string(41) "Doctrine\ORM\Attributes\InverseJoinColumn"
+array(3) {
+ [0]=>
+ string(14) "phonenumber_id"
+ [1]=>
+ string(2) "id"
+ [2]=>
+ string(6) "unique"
+}
diff --git a/Zend/tests/attributes/010_unsupported_const_expression.phpt b/Zend/tests/attributes/010_unsupported_const_expression.phpt
new file mode 100644
index 0000000000..50afa6c4f6
--- /dev/null
+++ b/Zend/tests/attributes/010_unsupported_const_expression.phpt
@@ -0,0 +1,11 @@
+--TEST--
+Attribute arguments support only const expressions.
+--FILE--
+<?php
+
+<<A1(foo())>>
+class C1 { }
+
+?>
+--EXPECTF--
+Fatal error: Constant expression contains invalid operations in %s
diff --git a/Zend/tests/attributes/011-inheritance.phpt b/Zend/tests/attributes/011-inheritance.phpt
new file mode 100644
index 0000000000..007cd5991e
--- /dev/null
+++ b/Zend/tests/attributes/011-inheritance.phpt
@@ -0,0 +1,99 @@
+--TEST--
+Attributes comply with inheritance rules.
+--FILE--
+<?php
+
+<<A2>>
+class C1
+{
+ <<A1>>
+ public function foo() { }
+}
+
+class C2 extends C1
+{
+ public function foo() { }
+}
+
+class C3 extends C1
+{
+ <<A1>>
+ public function bar() { }
+}
+
+$ref = new \ReflectionClass(C1::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes()));
+print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));
+
+$ref = new \ReflectionClass(C2::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes()));
+print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));
+
+$ref = new \ReflectionClass(C3::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getAttributes()));
+print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));
+
+echo "\n";
+
+trait T1
+{
+ <<A2>>
+ public $a;
+}
+
+class C4
+{
+ use T1;
+}
+
+class C5
+{
+ use T1;
+
+ public $a;
+}
+
+$ref = new \ReflectionClass(T1::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes()));
+
+$ref = new \ReflectionClass(C4::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes()));
+
+$ref = new \ReflectionClass(C5::class);
+print_r(array_map(fn ($a) => $a->getName(), $ref->getProperty('a')->getAttributes()));
+
+?>
+--EXPECT--
+Array
+(
+ [0] => A2
+)
+Array
+(
+ [0] => A1
+)
+Array
+(
+)
+Array
+(
+)
+Array
+(
+)
+Array
+(
+ [0] => A1
+)
+
+Array
+(
+ [0] => A2
+)
+Array
+(
+ [0] => A2
+)
+Array
+(
+)
diff --git a/Zend/tests/attributes/012-ast-export.phpt b/Zend/tests/attributes/012-ast-export.phpt
new file mode 100644
index 0000000000..47176b1b0d
--- /dev/null
+++ b/Zend/tests/attributes/012-ast-export.phpt
@@ -0,0 +1,54 @@
+--TEST--
+Attributes AST can be exported.
+--FILE--
+<?php
+
+assert(0 && ($a = <<A1>><<A2>> function ($a, <<A3(1)>> $b) { }));
+
+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 = function () {
+ <<A1>> class Test1 { }
+ <<A2>> interface Test2 { }
+ <<A3>> trait Test3 { }
+}));
+
+?>
+--EXPECTF--
+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 = new <<A1>> class {
+ <<A1>>
+ <<A2>>
+ const FOO = 'foo';
+ <<A2>>
+ public $x;
+ <<A3>>
+ public function a() {
+ }
+
+})) failed in %s on line %d
+
+Warning: assert(): assert(0 && ($a = function () {
+ <<A1>>
+ class Test1 {
+ }
+
+ <<A2>>
+ interface Test2 {
+ }
+
+ <<A3>>
+ trait Test3 {
+ }
+
+})) failed in %s on line %d
diff --git a/Zend/tests/attributes/013_class_scope.phpt b/Zend/tests/attributes/013_class_scope.phpt
new file mode 100644
index 0000000000..3bb2b303ba
--- /dev/null
+++ b/Zend/tests/attributes/013_class_scope.phpt
@@ -0,0 +1,117 @@
+--TEST--
+Attributes make use of class scope.
+--FILE--
+<?php
+
+<<A1(self::class, self::FOO)>>
+class C1
+{
+ <<A1(self::class, self::FOO)>>
+ private const FOO = 'foo';
+
+ <<A1(self::class, self::FOO)>>
+ public $a;
+
+ <<A1(self::class, self::FOO)>>
+ public function bar(<<A1(self::class, self::FOO)>> $p) { }
+}
+
+$ref = new \ReflectionClass(C1::class);
+print_r($ref->getAttributes()[0]->getArguments());
+print_r($ref->getReflectionConstant('FOO')->getAttributes()[0]->getArguments());
+print_r($ref->getProperty('a')->getAttributes()[0]->getArguments());
+print_r($ref->getMethod('bar')->getAttributes()[0]->getArguments());
+print_r($ref->getMethod('bar')->getParameters()[0]->getAttributes()[0]->getArguments());
+
+echo "\n";
+
+trait T1
+{
+ <<A1(self::class, self::FOO)>>
+ public function foo() { }
+}
+
+class C2
+{
+ use T1;
+
+ private const FOO = 'bar';
+}
+
+$ref = new \ReflectionClass(C2::class);
+print_r($ref->getMethod('foo')->getAttributes()[0]->getArguments());
+
+$ref = new \ReflectionClass(T1::class);
+$attr = $ref->getMethod('foo')->getAttributes()[0];
+
+try {
+ $attr->getArguments();
+} catch (\Error $e) {
+ var_dump('ERROR 1', $e->getMessage());
+}
+
+echo "\n";
+
+class C3
+{
+ private const FOO = 'foo';
+
+ public static function foo()
+ {
+ return new <<A1(self::class, self::FOO)>> class() {
+ private const FOO = 'bar';
+
+ <<A1(self::class, self::FOO)>>
+ public function bar() { }
+ };
+ }
+}
+
+$ref = new \ReflectionObject(C3::foo());
+
+$args = $ref->getAttributes()[0]->getArguments();
+var_dump($args[0] == $ref->getName(), $args[1]);
+
+$args = $ref->getMethod('bar')->getAttributes()[0]->getArguments();
+var_dump($args[0] == $ref->getName(), $args[1]);
+
+?>
+--EXPECT--
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+
+Array
+(
+ [0] => C2
+ [1] => bar
+)
+string(7) "ERROR 1"
+string(36) "Undefined class constant 'self::FOO'"
+
+bool(true)
+string(3) "bar"
+bool(true)
+string(3) "bar"
diff --git a/Zend/tests/attributes/014_class_const_group.phpt b/Zend/tests/attributes/014_class_const_group.phpt
new file mode 100644
index 0000000000..18ff93683a
--- /dev/null
+++ b/Zend/tests/attributes/014_class_const_group.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Attributes cannot be applied to groups of class constants.
+--FILE--
+<?php
+
+class C1
+{
+ <<A1>>
+ public const A = 1, B = 2;
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot apply attributes to a group of constants in %s
diff --git a/Zend/tests/attributes/015_property_group.phpt b/Zend/tests/attributes/015_property_group.phpt
new file mode 100644
index 0000000000..493e4ae260
--- /dev/null
+++ b/Zend/tests/attributes/015_property_group.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Attributes cannot be applied to groups of properties.
+--FILE--
+<?php
+
+class C1
+{
+ <<A1>>
+ public $x, $y;
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot apply attributes to a group of properties in %s
diff --git a/Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt b/Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt
new file mode 100644
index 0000000000..c96b49cbd7
--- /dev/null
+++ b/Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Attributes: Compiler Attributes can check for target declarations
+--SKIPIF--
+<?php
+if (!extension_loaded('zend-test')) {
+ echo "skip requires zend-test extension\n";
+}
+--FILE--
+<?php
+
+<<ZendTestAttribute>>
+function foo() {
+}
+--EXPECTF--
+Fatal error: Only classes can be marked with <<ZendTestAttribute>> in %s
diff --git a/Zend/tests/attributes/017_closure_scope.phpt b/Zend/tests/attributes/017_closure_scope.phpt
new file mode 100644
index 0000000000..f57b4b73b3
--- /dev/null
+++ b/Zend/tests/attributes/017_closure_scope.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Attributes make use of closure scope.
+--FILE--
+<?php
+
+class Test1
+{
+ private const FOO = 'bar';
+}
+
+class C1
+{
+ private const FOO = 'foo';
+
+ public static function foo()
+ {
+ return <<A1(self::class, self::FOO)>> function (<<A1(self::class, self::FOO)>> $p) { };
+ }
+}
+
+$ref = new \ReflectionFunction(C1::foo());
+print_r($ref->getAttributes()[0]->getArguments());
+print_r($ref->getParameters()[0]->getAttributes()[0]->getArguments());
+
+echo "\n";
+
+$ref = new \ReflectionFunction(C1::foo()->bindTo(null, Test1::class));
+print_r($ref->getAttributes()[0]->getArguments());
+print_r($ref->getParameters()[0]->getAttributes()[0]->getArguments());
+
+?>
+--EXPECT--
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+Array
+(
+ [0] => C1
+ [1] => foo
+)
+
+Array
+(
+ [0] => Test1
+ [1] => bar
+)
+Array
+(
+ [0] => Test1
+ [1] => bar
+)