diff --git a/src/Definition/AttributesContainer.php b/src/Definition/AttributesContainer.php index ba6ae279..f60353c8 100644 --- a/src/Definition/AttributesContainer.php +++ b/src/Definition/AttributesContainer.php @@ -7,18 +7,28 @@ use Traversable; use function array_filter; +use function array_map; use function array_values; use function count; +use function is_a; -/** @internal */ +/** + * @phpstan-type AttributeParam = array{class: class-string, callback: callable(): object} + * + * @internal + */ final class AttributesContainer implements Attributes { private static self $empty; - /** @var array */ + /** @var list */ private array $attributes; - public function __construct(object ...$attributes) + /** + * @no-named-arguments + * @param AttributeParam ...$attributes + */ + public function __construct(array ...$attributes) { $this->attributes = $attributes; } @@ -31,7 +41,7 @@ public static function empty(): self public function has(string $className): bool { foreach ($this->attributes as $attribute) { - if ($attribute instanceof $className) { + if (is_a($attribute['class'], $className, true)) { return true; } } @@ -41,9 +51,15 @@ public function has(string $className): bool public function ofType(string $className): array { - return array_values(array_filter( + $attributes = array_filter( $this->attributes, - static fn (object $attribute): bool => $attribute instanceof $className + static fn (array $attribute): bool => is_a($attribute['class'], $className, true) + ); + + /** @phpstan-ignore-next-line */ + return array_values(array_map( + fn (array $attribute) => $attribute['callback'](), + $attributes )); } @@ -57,6 +73,8 @@ public function count(): int */ public function getIterator(): Traversable { - return yield from $this->attributes; + foreach ($this->attributes as $attribute) { + yield $attribute['callback'](); + } } } diff --git a/src/Definition/NativeAttributes.php b/src/Definition/NativeAttributes.php index f4a7b980..064702ae 100644 --- a/src/Definition/NativeAttributes.php +++ b/src/Definition/NativeAttributes.php @@ -32,7 +32,12 @@ public function __construct(ReflectionClass|ReflectionProperty|ReflectionMethod| array_map( static function (ReflectionAttribute $attribute) { try { - return $attribute->newInstance(); + $instance = $attribute->newInstance(); + + return [ + 'class' => $attribute->getName(), + 'callback' => fn () => $instance + ]; } catch (Error) { // Race condition when the attribute is affected to a property/parameter // that was PROMOTED, in this case the attribute will be applied to both diff --git a/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php b/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php index d04e49b1..88706307 100644 --- a/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php +++ b/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php @@ -34,21 +34,15 @@ public function compile(Attributes $attributes): string private function compileNativeAttributes(NativeAttributes $attributes): string { - $attributes = $attributes->definition(); - - if (count($attributes) === 0) { - return '[]'; - } - $attributesListCode = []; - foreach ($attributes as $className => $arguments) { + foreach ($attributes->definition() as $className => $arguments) { $argumentsCode = $this->compileAttributeArguments($arguments); - $attributesListCode[] = "new $className($argumentsCode)"; + $attributesListCode[] = "['class' => '$className', 'callback' => fn () => new $className($argumentsCode)]"; } - return '...[' . implode(",\n", $attributesListCode) . ']'; + return implode(', ', $attributesListCode); } /** diff --git a/tests/Unit/Definition/AttributesContainerTest.php b/tests/Unit/Definition/AttributesContainerTest.php index 4a2365f4..73a841c9 100644 --- a/tests/Unit/Definition/AttributesContainerTest.php +++ b/tests/Unit/Definition/AttributesContainerTest.php @@ -11,6 +11,9 @@ use PHPUnit\Framework\TestCase; use stdClass; +/** + * @phpstan-import-type AttributeParam from AttributesContainer + */ final class AttributesContainerTest extends TestCase { public function test_empty_attributes_is_empty_and_remains_the_same_instance(): void @@ -25,22 +28,34 @@ public function test_empty_attributes_is_empty_and_remains_the_same_instance(): public function test_attributes_are_countable(): void { - $attributes = new AttributesContainer(new stdClass(), new stdClass(), new stdClass()); + $attributes = [ + $this->attribute(new stdClass()), + $this->attribute(new stdClass()), + $this->attribute(new stdClass()), + ]; - self::assertCount(3, $attributes); + $container = new AttributesContainer(...$attributes); + + self::assertCount(3, $container); } public function test_attributes_are_traversable(): void { - $attributes = [new stdClass(), new stdClass(), new stdClass()]; + $objects = [new stdClass(), new stdClass(), new stdClass()]; + $attributes = [ + $this->attribute($objects[0]), + $this->attribute($objects[1]), + $this->attribute($objects[2]), + ]; + $container = new AttributesContainer(...$attributes); - self::assertSame($attributes, iterator_to_array($container)); + self::assertSame($objects, iterator_to_array($container)); } public function test_attributes_has_type_checks_all_attributes(): void { - $attributes = new AttributesContainer(new stdClass()); + $attributes = new AttributesContainer($this->attribute(new stdClass())); self::assertTrue($attributes->has(stdClass::class)); self::assertFalse($attributes->has(DateTimeInterface::class)); @@ -51,11 +66,22 @@ public function test_attributes_of_type_filters_on_given_class_name(): void $object = new stdClass(); $date = new DateTime(); - $attributes = new AttributesContainer($object, $date); + $attributes = new AttributesContainer($this->attribute($object), $this->attribute($date)); $filteredAttributes = $attributes->ofType(DateTimeInterface::class); self::assertContainsEquals($date, $filteredAttributes); self::assertNotContains($object, $filteredAttributes); self::assertSame($date, $filteredAttributes[0]); } + + /** + * @return AttributeParam + */ + private function attribute(object $object): array + { + return [ + 'class'=> $object::class, + 'callback' => fn () => $object + ]; + } }