Passed
Push — error-message ( b7292a...ff40fa )
by Akihito
04:28 queued 02:25
created

AssistedInterceptor::getInterface()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 3
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use Ray\Aop\MethodInterceptor;
8
use Ray\Aop\MethodInvocation;
9
use Ray\Aop\ReflectionMethod;
10
use Ray\Di\Di\Assisted;
11
use Ray\Di\Di\Named;
12
use ReflectionNamedType;
13
use ReflectionParameter;
14
use ReflectionType;
15
16
use function assert;
17
use function class_exists;
18
use function in_array;
19
use function interface_exists;
20
use function parse_str;
21
22
/**
23
 * Assisted injection interceptor for @ Assisted annotated parameter
24
 */
25
final class AssistedInterceptor implements MethodInterceptor
26
{
27
    /** @var InjectorInterface */
28
    private $injector;
29
30
    /** @var MethodInvocationProvider */
31
    private $methodInvocationProvider;
32
33
    public function __construct(InjectorInterface $injector, MethodInvocationProvider $methodInvocationProvider)
34
    {
35
        $this->injector = $injector;
36
        $this->methodInvocationProvider = $methodInvocationProvider;
37
    }
38
39
    /**
40
     * Intercepts any method and injects instances of the missing arguments
41
     * when they are type hinted
42
     *
43
     * @return mixed
44
     */
45
    public function invoke(MethodInvocation $invocation)
46
    {
47
        $method = $invocation->getMethod();
48
        $assisted = $method->getAnnotation(Assisted::class);
49
        $parameters = $method->getParameters();
50
        $arguments = $invocation->getArguments()->getArrayCopy();
51
        if ($assisted instanceof Assisted) {
52
            $this->methodInvocationProvider->set($invocation);
53
            $arguments = $this->injectAssistedParameters($method, $assisted, $parameters, $arguments);
54
        }
55
56
        $invocation->getArguments()->exchangeArray($arguments);
57
58
        return $invocation->proceed();
59
    }
60
61
    /**
62
     * @param array<ReflectionParameter> $parameters
63
     * @param array<int, mixed>          $arguments
64
     *
65
     * @return array<int, mixed>
66
     *
67
     * @internal param int $cntArgs
68
     */
69
    public function injectAssistedParameters(ReflectionMethod $method, Assisted $assisted, array $parameters, array $arguments): array
70
    {
71
        foreach ($parameters as $parameter) {
72
            if (! in_array($parameter->getName(), $assisted->values, true)) {
73
                continue;
74
            }
75
76
            $type = $parameter->getType();
77
            $interface = $this->getInterface($type);
78
            $name = $this->getName($method, $parameter);
79
            $pos = $parameter->getPosition();
80
            assert(class_exists($interface) || interface_exists($interface) || $interface === '');
81
            /** @psalm-suppress MixedAssignment */
82
            $arguments[$pos] = $this->injector->getInstance($interface, $name);
83
        }
84
85
        return $arguments;
86
    }
87
88
    /**
89
     * Return dependency "name"
90
     */
91
    private function getName(ReflectionMethod $method, ReflectionParameter $parameter): string
92
    {
93
        $named = $method->getAnnotation(Named::class);
94
        if (! $named instanceof Named) {
95
            return Name::ANY;
96
        }
97
98
        parse_str($named->value, $names);
99
        $paramName = $parameter->getName();
100
        if (isset($names[$paramName])) {
101
            return (string) $names[$paramName];
102
        }
103
104
        return Name::ANY;
105
    }
106
107
    private function getInterface(?ReflectionType $type): string
108
    {
109
        return $type instanceof ReflectionNamedType && ! in_array($type->getName(), Argument::UNBOUND_TYPE, true) ? $type->getName() : '';
110
    }
111
}
112