AssistedInjectInterceptor::invoke()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 20
c 0
b 0
f 0
rs 9.9
cc 4
nc 3
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\Di\Di\Assisted;
10
use Ray\Di\Di\Inject;
11
use Ray\Di\Di\InjectInterface;
12
use Ray\Di\Di\Named;
13
use ReflectionAttribute;
14
use ReflectionNamedType;
15
use ReflectionParameter;
16
17
use function assert;
18
use function call_user_func_array;
19
use function get_class;
20
use function in_array;
21
use function is_callable;
22
23
/**
24
 * Assisted injection interceptor for #[Inject] attributed parameter
25
 */
26
final class AssistedInjectInterceptor implements MethodInterceptor
27
{
28
    /** @var InjectorInterface */
29
    private $injector;
30
31
    /** @var MethodInvocationProvider */
32
    private $methodInvocationProvider;
33
34
    public function __construct(InjectorInterface $injector, MethodInvocationProvider $methodInvocationProvider)
35
    {
36
        $this->injector = $injector;
37
        $this->methodInvocationProvider = $methodInvocationProvider;
38
    }
39
40
    /**
41
     * @return mixed
42
     */
43
    public function invoke(MethodInvocation $invocation)
44
    {
45
        $this->methodInvocationProvider->set($invocation);
46
        $params = $invocation->getMethod()->getParameters();
47
        $namedArguments = $this->getNamedArguments($invocation);
48
        foreach ($params as $param) {
49
            /** @var list<ReflectionAttribute> $inject */
50
            $inject = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); // @phpstan-ignore-line
51
            /** @var list<ReflectionAttribute> $assisted */
52
            $assisted = $param->getAttributes(Assisted::class);
53
            if (isset($assisted[0]) || isset($inject[0])) {
54
                /** @psalm-suppress MixedAssignment */
55
                $namedArguments[$param->getName()] = $this->getDependency($param);
56
            }
57
        }
58
59
        $callable = [$invocation->getThis(), $invocation->getMethod()->getName()];
60
        assert(is_callable($callable));
61
62
        return call_user_func_array($callable, $namedArguments);
63
    }
64
65
    /**
66
     * @return array<string, mixed>
67
     */
68
    private function getNamedArguments(MethodInvocation $invocation): array
69
    {
70
        $args = $invocation->getArguments();
71
        $params = $invocation->getMethod()->getParameters();
72
        $namedParams = [];
73
        foreach ($params as $param) {
74
            $pos = $param->getPosition();
75
            if (isset($args[$pos])) {
76
                /** @psalm-suppress MixedAssignment */
77
                $namedParams[$param->getName()] = $args[$pos];
78
            }
79
        }
80
81
        return $namedParams;
82
    }
83
84
    /**
85
     * @return mixed
86
     */
87
    private function getDependency(ReflectionParameter $param)
88
    {
89
        $named = (string) $this->getName($param);
90
        $type = $param->getType();
91
        assert($type instanceof ReflectionNamedType || $type === null);
92
        $typeName = $type ? $type->getName() : '';
0 ignored issues
show
introduced by
$type is of type null, thus it always evaluated to false.
Loading history...
93
        $interface = in_array($typeName, Argument::UNBOUND_TYPE) ? '' : $typeName;
94
95
        /** @var class-string $interface */
96
        return $this->injector->getInstance($interface, $named);
97
    }
98
99
    private function getName(ReflectionParameter $param): ?string
100
    {
101
        /** @var list<ReflectionAttribute> $nameds */
102
        $nameds = $param->getAttributes(Named::class);
103
        if (isset($nameds[0])) {
104
            $named = $nameds[0]->newInstance();
105
            assert($named instanceof Named);
106
107
            return $named->value;
108
        }
109
110
        if ($param->getAttributes(Inject::class)) {
111
            return null;
112
        }
113
114
        return $this->getCustomInject($param);
115
    }
116
117
    /**
118
     * @return ?class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in ?class-string.
Loading history...
119
     */
120
    private function getCustomInject(ReflectionParameter $param): ?string
121
    {
122
        /** @var list<ReflectionAttribute> $injects */
123
        $injects = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF);
124
        if (! $injects) {
0 ignored issues
show
introduced by
$injects is of type Ray\Di\list, thus it always evaluated to true.
Loading history...
125
            return null;
126
        }
127
128
        $inject = $injects[0]->newInstance();
129
        assert($inject instanceof InjectInterface);
130
131
        return get_class($inject);
132
    }
133
}
134