Skip to content
Open
6 changes: 2 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
],
"require": {
"php": "^7.2 || ^8.0",
"doctrine/annotations": "^1.8 || ^2.0",
"doctrine/cache": "^1.10 || ^2.1",
"koriym/attributes": "^1.0",
"koriym/null-object": "^1.0",
"koriym/param-reader": "^1.0",
"koriym/printo": "^1.0",
"ray/aop": "^2.14",
"ray/di": "^2.18",
"ray/aop": "php82-dev",
"ray/di": "php82-dev",
"symfony/polyfill-php83": "^1.33"
},
"require-dev": {
Expand Down
50 changes: 41 additions & 9 deletions src/InjectionPoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
use Ray\Aop\ReflectionMethod;
use Ray\Di\Di\Qualifier;
use Ray\Di\InjectionPointInterface;
use Ray\ServiceLocator\ServiceLocator;
use ReflectionAttribute;
use ReflectionException;
use ReflectionParameter;

use function assert;
use function class_exists;
use function count;

/**
* @psalm-import-type ScriptDir from Types
Expand Down Expand Up @@ -95,20 +96,51 @@ public function getQualifiers(): array
* {@inheritDoc}
*
* @return object|null
*
* @throws ReflectionException
*/
public function getQualifier()
{
$reader = ServiceLocator::getReader();
$annotations = $reader->getMethodAnnotations($this->getMethod());
foreach ($annotations as $annotation) {
$maybeQualifers = $reader->getClassAnnotations(new \ReflectionClass($annotation));
foreach ($maybeQualifers as $maybeQualifer) {
if ($maybeQualifer instanceof Qualifier) {
return $annotation;
}
// Try method attributes first
$parameter = $this->getParameter();
$class = $parameter->getDeclaringClass();
$methodName = $parameter->getDeclaringFunction()->getShortName();
assert($class instanceof \ReflectionClass);

$nativeMethod = new \ReflectionMethod($class->getName(), $methodName);
$methodAttributes = $nativeMethod->getAttributes();

foreach ($methodAttributes as $attribute) {
if ($this->isQualifier($attribute)) {
return $attribute->newInstance();
}
}

// Try parameter attributes
$paramAttributes = $parameter->getAttributes();

foreach ($paramAttributes as $attribute) {
if ($this->isQualifier($attribute)) {
return $attribute->newInstance();
}
}

return null;
}

/**
* @phpstan-param ReflectionAttribute<object> $attribute
*
* @throws ReflectionException
*
* @psalm-suppress TooManyTemplateParams
*/
private function isQualifier(ReflectionAttribute $attribute): bool
{
$attributeClass = $attribute->getName();
$reflectionClass = new \ReflectionClass($attributeClass);
$classAttributes = $reflectionClass->getAttributes(Qualifier::class);

return count($classAttributes) > 0;
}
}
1 change: 1 addition & 0 deletions tests/Fake/FakeLoggerConsumer.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(InjectorInterface $injector)
/**
* @FakeLoggerInject(type="MEMORY")
*/
#[FakeLoggerInject(type: 'MEMORY')]
public function setLogger(FakeLoggerInterface $logger)
{
$this->logger = $logger;
Expand Down
19 changes: 19 additions & 0 deletions tests/Fake/FakeLoggerInject.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ final class FakeLoggerInject implements InjectInterface
/** @Enum({"MEMORY", "FILE", "DB"}) */
public $type;

/**
* @param string|array<string, string>|null $type
*/
public function __construct($type = null)
{
if (is_array($type) && isset($type['type'])) {
// Doctrine Annotations format: @FakeLoggerInject(type="MEMORY")
$this->type = $type['type'];
} elseif (is_string($type)) {
// Attribute format: #[FakeLoggerInject(type: 'MEMORY')]
$this->type = $type;
} elseif (is_array($type) && isset($type['value'])) {
// Doctrine Annotations format: @FakeLoggerInject("MEMORY")
$this->type = $type['value'];
} else {
$this->type = $type ?? 'MEMORY';
}
}

public function isOptional()
{
return true;
Expand Down
Loading