-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add Attributes based on attributes_v2 RFC. #5394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
767dad1
8d1f6db
52b2a2d
a8d9751
8192d8b
1a270b2
83fea2c
8697ccf
6256e24
9e15b99
5572da5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
beberlei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
?> | ||
--EXPECTF-- | ||
string(28) "My\Attributes\SingleArgument" | ||
array(1) { | ||
[0]=> | ||
string(11) "Hello World" | ||
} | ||
object(My\Attributes\SingleArgument)#3 (1) { | ||
["argumentValue"]=> | ||
string(11) "Hello World" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
--TEST-- | ||
Attributes can deal with AST nodes. | ||
--FILE-- | ||
<?php | ||
|
||
define('V1', strtoupper(php_sapi_name())); | ||
beberlei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<<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)>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a bit of ambiguity here when it comes to cases like this: class Test {
public function method() {
return new <<A(self::class)>> class {};
}
} Here More ambiguous cases are: <<A(self::class)>>
trait T {} In traits, $fn = <<A(self::class)>> function() {};
$fn->bindTo(null, X::class); In closures, The behavior for these cases doesn't seem to be specified in the RFC. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW I expect that just adding tests with current behavior will yield something that is pretty reasonable. Making the scope the scope of the entity on which the attribute is declared is necessary to make things like accessing private class constants work (which it probably should?) so there might not be much real choice here. |
||
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()); | ||
} | ||
|
||
?> | ||
beberlei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
--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" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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")>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to be complete, there's also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant this one a bit more literally: |
||
<<Attributes\Table()>> | ||
function foo() { | ||
} | ||
} | ||
|
||
namespace { | ||
class Entity {} | ||
|
||
dump_attributes((new ReflectionFunction('Foo\foo'))->getAttributes()); | ||
} | ||
beberlei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
?> | ||
--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) { | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.