diff --git a/docs/attributes.md b/docs/attributes.md index 97f73c7..a626738 100644 --- a/docs/attributes.md +++ b/docs/attributes.md @@ -205,6 +205,7 @@ will cause a `RealRoute` to be appended in the routes array. use Aura\Di\Attribute\AttributeConfigFor; use Aura\Di\ClassScanner\AttributeConfigInterface; use Aura\Di\ClassScanner\AttributeSpecification; +use Aura\Di\ClassScanner\ClassSpecification; use Aura\Di\Container; #[\Attribute] @@ -213,7 +214,7 @@ class Route implements AttributeConfigInterface { public function __construct(private string $method, private string $uri) { } - public static function define(Container $di, AttributeSpecification $specification): void + public static function define(Container $di, AttributeSpecification $specification, ClassSpecification $classSpecification): void { if ($specification->getAttributeTarget() === \Attribute::TARGET_METHOD) { /** @var self $attribute */ @@ -259,15 +260,16 @@ you can create an implementation of `AttributeConfigInterface` yourself, and ann use Aura\Di\Attribute\AttributeConfigFor; use Aura\Di\ClassScanner\AttributeConfigInterface; use Aura\Di\ClassScanner\AttributeSpecification; +use Aura\Di\ClassScanner\ClassSpecification; use Aura\Di\Container; use Symfony\Component\Routing\Attribute\Route; #[AttributeConfigFor(Route::class)] class SymfonyRouteAttributeConfig implements AttributeConfigInterface { - public static function define(Container $di, AttributeSpecification $specification): void + public static function define(Container $di, AttributeSpecification $specification, ClassSpecification $classSpecification): void { - if ($specification->getAttributeTarget() === \Attribute::TARGET_METHOD) { + if ($specification->isMethodAttribute()) { /** @var Route $attribute */ $attribute = $specification->getAttributeInstance(); diff --git a/src/ClassScanner/AnnotatedInjectAttributeConfig.php b/src/ClassScanner/AnnotatedInjectAttributeConfig.php index 1391241..3fab1bb 100644 --- a/src/ClassScanner/AnnotatedInjectAttributeConfig.php +++ b/src/ClassScanner/AnnotatedInjectAttributeConfig.php @@ -15,14 +15,16 @@ final class AnnotatedInjectAttributeConfig implements AttributeConfigInterface { - private const CONSTRUCTOR_NAME = '__construct'; - - public static function define(Container $di, AttributeSpecification $specification): void + public static function define( + Container $di, + AttributeSpecification $attributeSpecification, + ClassSpecification $classSpecification + ): void { - /** @var AnnotatedInjectInterface $attribute */ - $attribute = $specification->getAttributeInstance(); - if ($specification->getTargetMethod() === self::CONSTRUCTOR_NAME && $specification->getAttributeTarget() === \Attribute::TARGET_PARAMETER) { - $di->params[$specification->getClassName()][$specification->getTargetParameter()] = $attribute->inject(); + if ($attributeSpecification->isConstructorParameterAttribute()) { + /** @var AnnotatedInjectInterface $attribute */ + $attribute = $attributeSpecification->getAttributeInstance(); + $di->params[$attributeSpecification->getClassName()][$attributeSpecification->getTargetParameter()] = $attribute->inject(); } } } \ No newline at end of file diff --git a/src/ClassScanner/AttributeConfigInterface.php b/src/ClassScanner/AttributeConfigInterface.php index 51e606a..7be8260 100644 --- a/src/ClassScanner/AttributeConfigInterface.php +++ b/src/ClassScanner/AttributeConfigInterface.php @@ -14,5 +14,9 @@ interface AttributeConfigInterface { - public static function define(Container $di, AttributeSpecification $specification): void; + public static function define( + Container $di, + AttributeSpecification $attributeSpecification, + ClassSpecification $classSpecification + ): void; } \ No newline at end of file diff --git a/src/ClassScanner/AttributeSpecification.php b/src/ClassScanner/AttributeSpecification.php index fb66ea2..c54b519 100644 --- a/src/ClassScanner/AttributeSpecification.php +++ b/src/ClassScanner/AttributeSpecification.php @@ -6,6 +6,7 @@ final class AttributeSpecification { + private const CONSTRUCTOR_NAME = '__construct'; private object $attributeInstance; private string $className; private int $attributeTarget; @@ -64,4 +65,34 @@ public function getTargetConstant(): ?string { return $this->targetConfig['constant'] ?? null; } + + public function isConstructorParameterAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_PARAMETER && ($this->targetConfig['method'] ?? '') === self::CONSTRUCTOR_NAME; + } + + public function isMethodAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_METHOD; + } + + public function isClassAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_CLASS; + } + + public function isParameterAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_PARAMETER; + } + + public function isPropertyAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_PROPERTY; + } + + public function isClassConstantAttribute(): bool + { + return $this->attributeTarget === \Attribute::TARGET_CLASS_CONSTANT; + } } \ No newline at end of file diff --git a/src/ClassScanner/BlueprintAttributeConfig.php b/src/ClassScanner/BlueprintAttributeConfig.php index 0626a5d..7a80b02 100644 --- a/src/ClassScanner/BlueprintAttributeConfig.php +++ b/src/ClassScanner/BlueprintAttributeConfig.php @@ -14,8 +14,12 @@ final class BlueprintAttributeConfig implements AttributeConfigInterface { - public static function define(Container $di, AttributeSpecification $specification): void + public static function define( + Container $di, + AttributeSpecification $attributeSpecification, + ClassSpecification $classSpecification + ): void { - $di->params[$specification->getClassName()] = $di->params[$specification->getClassName()] ?? []; + $di->params[$attributeSpecification->getClassName()] = $di->params[$attributeSpecification->getClassName()] ?? []; } } \ No newline at end of file diff --git a/src/ClassScanner/ClassMap.php b/src/ClassScanner/ClassMap.php index 466aa6b..4ff367d 100644 --- a/src/ClassScanner/ClassMap.php +++ b/src/ClassScanner/ClassMap.php @@ -11,7 +11,7 @@ final class ClassMap private array $scanPaths; /** @var array */ private array $filesToClass = []; - /** @var array> */ + /** @var array */ private array $classesToAttributes = []; /** @@ -23,13 +23,10 @@ public function __construct(array $scanPaths, string $basePath) $this->basePath = $basePath; } - /** - * @param array $attributeSpecifications - */ - public function addClass(string $class, string $filename, array $attributeSpecifications): void + public function addClass(ClassSpecification $classSpecification): void { - $this->filesToClass[$filename] = $class; - $this->classesToAttributes[$class] = $attributeSpecifications; + $this->filesToClass[$classSpecification->getFilename()] = $classSpecification->getClassName(); + $this->classesToAttributes[$classSpecification->getClassName()] = $classSpecification; } public function remove(string $filename): void @@ -54,12 +51,13 @@ public function getFiles(): array return \array_keys($this->filesToClass); } - /** - * @return array - */ - public function getAttributeSpecificationsFor(string $className): array + public function getClassSpecificationFor(string $className): ?ClassSpecification { - return $this->classesToAttributes[$className] ?? []; + if (\array_key_exists($className, $this->classesToAttributes)) { + return $this->classesToAttributes[$className]; + } + + throw new \InvalidArgumentException('Cannot find class specification for: ' . $className); } /** @@ -67,7 +65,13 @@ public function getAttributeSpecificationsFor(string $className): array */ public function getAttributeSpecifications(): array { - return \array_merge([], ...array_values($this->classesToAttributes)); + return \array_merge( + [], + ...\array_values(array_map( + fn (ClassSpecification $classSpecification) => $classSpecification->getAttributes(), + $this->classesToAttributes + )) + ); } public function isAttributeClassFile(string $filename): bool @@ -81,14 +85,8 @@ public function isAttributeClassFile(string $filename): bool return false; } - $attributeSpecifications = $this->classesToAttributes[$class]; - foreach ($attributeSpecifications as $specification) { - if ($specification->getAttributeInstance() instanceof \Attribute) { - return true; - } - } - return false; + return $this->classesToAttributes[$class]->isAttributeClass(); } public function merge(ClassMap $other): ClassMap @@ -110,7 +108,7 @@ public function saveToFileHandle($fileHandle): void foreach ($this->filesToClass as $filename => $className) { $classMapJson['files'][$filename] = $className; - if ($attributeSpecifications = $this->getAttributeSpecificationsFor($className)) { + if ($attributeSpecifications = $this->getClassSpecificationFor($className)->getAttributes()) { $classMapJson['attributes'][$className] = \array_map( fn (AttributeSpecification $attribute) => \serialize($attribute), $attributeSpecifications @@ -143,11 +141,13 @@ public static function fromFileHandle($fileHandle): ClassMap foreach ($cacheContentsJson['files'] as $filename => $className) { $classMap->addClass( - $className, - $filename, - \array_map( - fn (string $serializedAttributeSpecification) => \unserialize($serializedAttributeSpecification), - $cacheContentsJson['attributes'][$className] ?? [] + new ClassSpecification( + $className, + $filename, + \array_map( + fn (string $serializedAttributeSpecification) => \unserialize($serializedAttributeSpecification), + $cacheContentsJson['attributes'][$className] ?? [] + ) ) ); } diff --git a/src/ClassScanner/ClassScannerConfig.php b/src/ClassScanner/ClassScannerConfig.php index aea275e..a1d3dfe 100644 --- a/src/ClassScanner/ClassScannerConfig.php +++ b/src/ClassScanner/ClassScannerConfig.php @@ -64,13 +64,15 @@ public function define(Container $di): void } } - foreach ($classMap->getAttributeSpecificationsFor($className) as $specification) { + $classSpecification = $classMap->getClassSpecificationFor($className); + foreach ($classSpecification->getAttributes() as $specification) { $attribute = $specification->getAttributeInstance(); if (\array_key_exists($attribute::class, $attributeConfigs)) { $configuredBy = $attributeConfigs[$attribute::class]; $configuredBy::define( $di, $specification, + $classSpecification ); } @@ -81,6 +83,7 @@ public function define(Container $di): void $configuredBy::define( $di, $specification, + $classSpecification ); } } diff --git a/src/ClassScanner/ClassSpecification.php b/src/ClassScanner/ClassSpecification.php new file mode 100644 index 0000000..ce5c25e --- /dev/null +++ b/src/ClassScanner/ClassSpecification.php @@ -0,0 +1,84 @@ + $attributes + */ + private array $attributes; + + /** + * @param array $attributes + */ + public function __construct( + string $className, + string $filename, + array $attributes = [], + ) + { + $this->className = $className; + $this->filename = $filename; + $this->attributes = $attributes; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getFilename(): string + { + return $this->filename; + } + + /** + * @return array + */ + public function getAttributes(): array + { + return $this->attributes; + } + + public function isAttributeClass(): bool + { + foreach ($this->attributes as $specification) { + if ($specification->getAttributeInstance() instanceof \Attribute) { + return true; + } + } + + return false; + } + + /** + * @return array + */ + public function getClassAttributes(): array + { + return \array_filter( + $this->attributes, + function (AttributeSpecification $specification) { + return $specification->getAttributeTarget() === \Attribute::TARGET_CLASS; + } + ); + } + + /** + * @return array + */ + public function getParameterAttributesForMethod(string $methodName): array + { + return \array_filter( + $this->attributes, + function (AttributeSpecification $specification) use ($methodName) { + return $specification->getTargetMethod() === $methodName; + } + ); + } +} \ No newline at end of file diff --git a/src/ClassScanner/ComposerMapGenerator.php b/src/ClassScanner/ComposerMapGenerator.php index a74c904..4798900 100644 --- a/src/ClassScanner/ComposerMapGenerator.php +++ b/src/ClassScanner/ComposerMapGenerator.php @@ -51,9 +51,11 @@ private function convertToClassMap(ClassMap $classMap, array $composerMap): Clas } $classMap->addClass( - $class, - $path, - [...$this->reflector->yieldAttributes($class)] + new ClassSpecification( + $class, + $path, + [...$this->reflector->yieldAttributes($class)] + ) ); } diff --git a/tests/ClassScanner/CachedFileGeneratorTest.php b/tests/ClassScanner/CachedFileGeneratorTest.php index 84f3761..e4d39cd 100644 --- a/tests/ClassScanner/CachedFileGeneratorTest.php +++ b/tests/ClassScanner/CachedFileGeneratorTest.php @@ -51,7 +51,7 @@ public function testAddingClass() $classMap2 = $cachedFileModificationGenerator->update($classMap, [$newFile]); $this->assertContains($newClassName, $classMap2->getClasses()); - $this->assertCount(1, $classMap2->getAttributeSpecificationsFor($newClassName)); + $this->assertCount(1, $classMap2->getClassSpecificationFor($newClassName)->getAttributes()); } public function testRemovingClass() diff --git a/tests/Fake/FakeWorkerAttribute.php b/tests/Fake/FakeWorkerAttribute.php index 1379573..386fbb3 100644 --- a/tests/Fake/FakeWorkerAttribute.php +++ b/tests/Fake/FakeWorkerAttribute.php @@ -5,6 +5,7 @@ use Aura\Di\Attribute\BlueprintNamespace; use Aura\Di\ClassScanner\AttributeConfigInterface; use Aura\Di\ClassScanner\AttributeSpecification; +use Aura\Di\ClassScanner\ClassSpecification; use Aura\Di\Container; #[\Attribute] @@ -19,15 +20,19 @@ public function __construct(int $someSetting = 1) $this->someSetting = $someSetting; } - public static function define(Container $di, AttributeSpecification $specification): void + public static function define( + Container $di, + AttributeSpecification $attributeSpecification, + ClassSpecification $classSpecification + ): void { /** @var self $attribute */ - $attribute = $specification->getAttributeInstance(); - if ($specification->getAttributeTarget() === \Attribute::TARGET_CLASS) { + $attribute = $attributeSpecification->getAttributeInstance(); + if ($attributeSpecification->getAttributeTarget() === \Attribute::TARGET_CLASS) { $di->values['worker'] = $di->values['worker'] ?? []; $di->values['worker'][] = [ 'someSetting' => $attribute->someSetting, - 'className' => $specification->getClassName(), + 'className' => $attributeSpecification->getClassName(), ]; } }