Skip to content

Commit

Permalink
add class specification class, allows attribute config to ask for rel…
Browse files Browse the repository at this point in the history
…ated attributes
  • Loading branch information
frederikbosch committed Aug 19, 2024
1 parent 49e2c94 commit 69fe40c
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 48 deletions.
8 changes: 5 additions & 3 deletions docs/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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 */
Expand Down Expand Up @@ -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();

Expand Down
16 changes: 9 additions & 7 deletions src/ClassScanner/AnnotatedInjectAttributeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
6 changes: 5 additions & 1 deletion src/ClassScanner/AttributeConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
31 changes: 31 additions & 0 deletions src/ClassScanner/AttributeSpecification.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

final class AttributeSpecification
{
private const CONSTRUCTOR_NAME = '__construct';
private object $attributeInstance;
private string $className;
private int $attributeTarget;
Expand Down Expand Up @@ -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;
}
}
8 changes: 6 additions & 2 deletions src/ClassScanner/BlueprintAttributeConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()] ?? [];
}
}
52 changes: 26 additions & 26 deletions src/ClassScanner/ClassMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class ClassMap
private array $scanPaths;
/** @var array<string, class-string> */
private array $filesToClass = [];
/** @var array<class-string, array<int, AttributeSpecification>> */
/** @var array<class-string, ClassSpecification> */
private array $classesToAttributes = [];

/**
Expand All @@ -23,13 +23,10 @@ public function __construct(array $scanPaths, string $basePath)
$this->basePath = $basePath;
}

/**
* @param array<int, AttributeSpecification> $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
Expand All @@ -54,20 +51,27 @@ public function getFiles(): array
return \array_keys($this->filesToClass);
}

/**
* @return array<int, AttributeSpecification>
*/
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);
}

/**
* @return array<int, AttributeSpecification>
*/
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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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] ?? []
)
)
);
}
Expand Down
5 changes: 4 additions & 1 deletion src/ClassScanner/ClassScannerConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}

Expand All @@ -81,6 +83,7 @@ public function define(Container $di): void
$configuredBy::define(
$di,
$specification,
$classSpecification
);
}
}
Expand Down
84 changes: 84 additions & 0 deletions src/ClassScanner/ClassSpecification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Aura\Di\ClassScanner;

final class ClassSpecification
{
private string $className;
private string $filename;
/**
* @param array<int, AttributeSpecification> $attributes
*/
private array $attributes;

/**
* @param array<int, AttributeSpecification> $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<int, AttributeSpecification>
*/
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<int, AttributeSpecification>
*/
public function getClassAttributes(): array
{
return \array_filter(
$this->attributes,
function (AttributeSpecification $specification) {
return $specification->getAttributeTarget() === \Attribute::TARGET_CLASS;
}
);
}

/**
* @return array<int, AttributeSpecification>
*/
public function getParameterAttributesForMethod(string $methodName): array
{
return \array_filter(
$this->attributes,
function (AttributeSpecification $specification) use ($methodName) {
return $specification->getTargetMethod() === $methodName;
}
);
}
}
8 changes: 5 additions & 3 deletions src/ClassScanner/ComposerMapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
)
);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/ClassScanner/CachedFileGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 69fe40c

Please sign in to comment.