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 in_array;
20
use function is_callable;
21
22
/**
23
 * @psalm-import-type NamedArguments from Types
24
 * @psalm-import-type InjectableValue from Types
25
 */
26
27
/**
28
 * Assisted injection interceptor for #[Inject] attributed parameter
29
 *
30
 * @psalm-import-type NamedArguments from Types
31
 */
32
final class AssistedInjectInterceptor implements MethodInterceptor
33
{
34
    public function __construct(private InjectorInterface $injector, private MethodInvocationProvider $methodInvocationProvider)
35
    {
36
    }
37
38
    /**
39
     * @return mixed
40
     */
41
    public function invoke(MethodInvocation $invocation)
42
    {
43
        $this->methodInvocationProvider->set($invocation);
44
        $params = $invocation->getMethod()->getParameters();
45
        $namedArguments = $this->getNamedArguments($invocation);
46
        foreach ($params as $param) {
47
            /** @var list<ReflectionAttribute> $inject */
48
            $inject = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); // @phpstan-ignore-line
49
            /** @var list<ReflectionAttribute> $assisted */
50
            $assisted = $param->getAttributes(Assisted::class);
51
            if (isset($assisted[0]) || isset($inject[0])) {
52
                /** @psalm-suppress MixedAssignment */
53
                $namedArguments[$param->getName()] = $this->getDependency($param);
54
            }
55
        }
56
57
        $callable = [$invocation->getThis(), $invocation->getMethod()->getName()];
58
        assert(is_callable($callable));
59
60
        return call_user_func_array($callable, $namedArguments);
61
    }
62
63
    /**
64
     * @param MethodInvocation<object> $invocation
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 instanceof ReflectionNamedType ? $type->getName() : '';
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 $inject::class;
132
    }
133
}
134