diff options
author | Benjamin Eberlei <kontakt@beberlei.de> | 2020-05-24 20:57:00 +0200 |
---|---|---|
committer | Benjamin Eberlei <kontakt@beberlei.de> | 2020-06-04 18:19:49 +0200 |
commit | a7908c2d11439a203be92e765b1e000498689e00 (patch) | |
tree | 134edc814397a6ffa5218bf5b2153983c09b152e /Zend/tests/attributes | |
parent | 970ac28e274b7edcd46bad4588d40d444d4f9074 (diff) | |
download | php-git-a7908c2d11439a203be92e765b1e000498689e00.tar.gz |
Add Attributes
Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
Diffstat (limited to 'Zend/tests/attributes')
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 +) |