diff --git a/.github/patch-types.php b/.github/patch-types.php index 3a998d4246..a2999b77b5 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -35,8 +35,11 @@ foreach ($loader->getClassMap() as $class => $file) { case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php80Dummy.php'): case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'): case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'): + case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php'): + case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'): case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/Php74.php') && \PHP_VERSION_ID < 70400: + case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/RepeatableAttribute.php'): continue 2; } diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index d9b43ae730..9ff534501b 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -105,6 +105,16 @@ class ReflectionCaster return $a; } + public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'name' => 'getName', + 'arguments' => 'getArguments', + ]); + + return $a; + } + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -151,7 +161,7 @@ class ReflectionCaster self::addMap($a, $c, [ 'extends' => 'getParentClass', 'implements' => 'getInterfaceNames', - 'constants' => 'getConstants', + 'constants' => 'getReflectionConstants', ]); foreach ($c->getProperties() as $n) { @@ -162,6 +172,8 @@ class ReflectionCaster $a[$prefix.'methods'][$n->name] = $n; } + self::addAttributes($a, $c, $prefix); + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { self::addExtra($a, $c); } @@ -206,6 +218,8 @@ class ReflectionCaster $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); } + self::addAttributes($a, $c, $prefix); + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { foreach ($v as $k => &$v) { if (\is_object($v)) { @@ -225,6 +239,16 @@ class ReflectionCaster return $a; } + public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); + + self::addAttributes($a, $c); + + return $a; + } + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); @@ -243,6 +267,8 @@ class ReflectionCaster 'allowsNull' => 'allowsNull', ]); + self::addAttributes($a, $c, $prefix); + if ($v = $c->getType()) { $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; } @@ -271,6 +297,8 @@ class ReflectionCaster public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + self::addAttributes($a, $c); self::addExtra($a, $c); return $a; @@ -377,7 +405,7 @@ class ReflectionCaster } } - private static function addMap(array &$a, \Reflector $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) + private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) { foreach ($map as $k => $m) { if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { @@ -389,4 +417,13 @@ class ReflectionCaster } } } + + private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($c->getAttributes() as $n) { + $a[$prefix.'attributes'][] = $n; + } + } + } } diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 938bd03856..eee19cd60b 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -32,8 +32,10 @@ abstract class AbstractCloner implements ClonerInterface 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php index a4183e21af..7cda962376 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo; +use Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes; use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass; /** @@ -36,9 +37,24 @@ ReflectionClass { 0 => "Reflector" %A] constants: array:3 [ - "IS_IMPLICIT_ABSTRACT" => 16 - "IS_EXPLICIT_ABSTRACT" => %d - "IS_FINAL" => %d + 0 => ReflectionClassConstant { + +name: "IS_IMPLICIT_ABSTRACT" + +class: "ReflectionClass" + modifiers: "public" + value: 16 + } + 1 => ReflectionClassConstant { + +name: "IS_EXPLICIT_ABSTRACT" + +class: "ReflectionClass" + modifiers: "public" + value: %d + } + 2 => ReflectionClassConstant { + +name: "IS_FINAL" + +class: "ReflectionClass" + modifiers: "public" + value: %d + } ] properties: array:%d [ "name" => ReflectionProperty { @@ -75,7 +91,7 @@ Closure($x) { $b: & 123 } file: "%sReflectionCasterTest.php" - line: "68 to 68" + line: "84 to 84" } EOTXT , $var @@ -242,6 +258,135 @@ EODUMP; $this->assertDumpMatchesFormat($expectedDump, $generator); } + /** + * @requires PHP 8 + */ + public function testReflectionClassWithAttribute() + { + $var = new \ReflectionClass(LotsOfAttributes::class); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionClass { + +name: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: [] + } + ] +%A +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionMethodWithAttribute() + { + $var = new \ReflectionMethod(LotsOfAttributes::class, 'someMethod'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionMethod { + +name: "someMethod" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:1 [ + 0 => "two" + ] + } + ] +%A +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionPropertyWithAttribute() + { + $var = new \ReflectionProperty(LotsOfAttributes::class, 'someProperty'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionProperty { + +name: "someProperty" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" +%A attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:2 [ + 0 => "one" + "extra" => "hello" + ] + } + ] +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionClassConstantWithAttribute() + { + $var = new \ReflectionClassConstant(LotsOfAttributes::class, 'SOME_CONSTANT'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionClassConstant { + +name: "SOME_CONSTANT" + +class: "Symfony\Component\VarDumper\Tests\Fixtures\LotsOfAttributes" + modifiers: "public" + value: "some value" + attributes: array:2 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\RepeatableAttribute" + arguments: array:1 [ + 0 => "one" + ] + } + 1 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\RepeatableAttribute" + arguments: array:1 [ + 0 => "two" + ] + } + ] +} +EOTXT + , $var); + } + + /** + * @requires PHP 8 + */ + public function testReflectionParameterWithAttribute() + { + $var = new \ReflectionParameter([LotsOfAttributes::class, 'someMethod'], 'someParameter'); + + $this->assertDumpMatchesFormat(<<< 'EOTXT' +ReflectionParameter { + +name: "someParameter" + position: 0 + attributes: array:1 [ + 0 => ReflectionAttribute { + name: "Symfony\Component\VarDumper\Tests\Fixtures\MyAttribute" + arguments: array:1 [ + 0 => "three" + ] + } + ] +%A +} +EOTXT + , $var); + } + public static function stub(): void { } diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php new file mode 100644 index 0000000000..9ac1dad6cb --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Fixtures; + +#[MyAttribute] +final class LotsOfAttributes +{ + #[RepeatableAttribute('one'), RepeatableAttribute('two')] + public const SOME_CONSTANT = 'some value'; + + #[MyAttribute('one', extra: 'hello')] + private string $someProperty; + + #[MyAttribute('two')] + public function someMethod( + #[MyAttribute('three')] string $someParameter + ): void { + } +} diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php new file mode 100644 index 0000000000..a466fd6b8a --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/MyAttribute.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Fixtures; + +use Attribute; + +#[Attribute] +final class MyAttribute +{ + public function __construct( + private string $foo = 'default', + private ?string $extra = null, + ) { + } + + public function getFoo(): string + { + return $this->foo; + } + + public function getExtra(): ?string + { + return $this->extra; + } +} diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/RepeatableAttribute.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/RepeatableAttribute.php new file mode 100644 index 0000000000..bb3b5995af --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/RepeatableAttribute.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Fixtures; + +use Attribute; + +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS_CONST | Attribute::TARGET_PROPERTY)] +final class RepeatableAttribute +{ + private string $string; + + public function __construct(string $string = 'default') + { + $this->string = $string; + } + + public function getString(): string + { + return $this->string; + } +}