diff --git a/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php b/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php index 10b9441c1a28e596cb20e320edd9f7936564db51..ce1aa0c083e115c65e21ba8335423640fc110291 100644 --- a/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php +++ b/core/lib/Drupal/Component/Annotation/Doctrine/StaticReflectionParser.php @@ -205,7 +205,7 @@ protected function parse() break; case T_CLASS: // Convert the attributes to fully qualified names. - $this->classAttributes = array_map(fn($name) => strtolower($this->fullySpecifyName($name)), $attributeNames); + $this->classAttributes = array_map(fn($name) => $this->fullySpecifyName($name), $attributeNames); if ($last_token !== T_PAAMAYIM_NEKUDOTAYIM && $last_token !== T_NEW) { $this->docComment['class'] = $docComment; $docComment = ''; @@ -346,7 +346,13 @@ public function getStaticReflectionParserForDeclaringClass($type, $name) public function hasClassAttribute(string $attribute): bool { $this->parse(); - return in_array('\\' . ltrim(strtolower($attribute), '\\'), $this->classAttributes, TRUE); + foreach ($this->classAttributes as $classAttribute) { + if (is_a($classAttribute, $attribute, TRUE)) { + return TRUE; + } + } + + return FALSE; } /** diff --git a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/Attribute/Nonexistent.php b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/Attribute/Nonexistent.php new file mode 100644 index 0000000000000000000000000000000000000000..45ed519aa40da8a60867f6d54203b4e13501ed2b --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/Attribute/Nonexistent.php @@ -0,0 +1,9 @@ +<?php + +namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute; + +// @phpstan-ignore-next-line +#[NonexistentAttribute] +final class Nonexistent +{ +} diff --git a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleAttribute.php b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleAttribute.php index 0275fabb56f80042ee169b920c2788f732848435..abcd69fa12918af9c65ac4045b72a3571eed91b7 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleAttribute.php +++ b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleAttribute.php @@ -3,6 +3,6 @@ namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes; #[\Attribute] -final class ExampleAttribute +final class ExampleAttribute extends ExampleParentAttribute { } diff --git a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleParentAttribute.php b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleParentAttribute.php new file mode 100644 index 0000000000000000000000000000000000000000..ea1eaaa0f89afe12683552ab30dd391c5a3589c1 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/Fixtures/ExtraAttributes/ExampleParentAttribute.php @@ -0,0 +1,8 @@ +<?php + +namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes; + +#[\Attribute] +class ExampleParentAttribute { + +} diff --git a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php index d929e48f6120b1cd902c38b818d61811e30335a9..85a99f3c353b6ee12467905c74629937385bead0 100644 --- a/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php +++ b/core/tests/Drupal/Tests/Component/Annotation/Doctrine/StaticReflectionParserTest.php @@ -15,8 +15,10 @@ class StaticReflectionParserTest extends TestCase { /** * @testWith ["AttributeClass", "\\Attribute", true] + * ["AttributeClass", "attribute", true] * ["AttributeClass", "Attribute", true] * ["AttributeClass", "\\DoesNotExist", false] + * ["Nonexistent", "NonexistentAttribute", false] * ["MultipleAttributes", "Attribute", true] * ["MultipleAttributes", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\AttributeClass", true] * ["MultipleAttributes", "DoesNotExist", false] @@ -26,15 +28,16 @@ class StaticReflectionParserTest extends TestCase { * ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] * ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true] * ["Relative", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\SubDir\\SubDirAttribute", true] + * ["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] + * ["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] + * ["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] + * ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] + * ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true] */ public function testAttribute(string $class, string $attribute_class, bool $expected) { $finder = MockFileFinder::create(__DIR__ . '/Fixtures/Attribute/' . $class . '.php'); $parser = new StaticReflectionParser('\\Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\' . $class, $finder); - $this->assertSame($expected, $parser->hasClassAttribute($attribute_class), "'$class' has '$attribute_class'"); - // Attribute names and namespaces are case-insensitive in PHP. Practically - // Composer autoloading makes this untrue but builtins like \Attribute are - // case-insensitive so we should support that. - $this->assertSame($expected, $parser->hasClassAttribute(strtoupper($attribute_class)), "'$class' has '$attribute_class'"); + $this->assertSame($expected, $parser->hasClassAttribute($attribute_class), "'$class' has attribute that is a '$attribute_class'"); } }