Issues (101)

src/di/AssistedInjectInterceptor.php (3 issues)

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
 * @psalm-import-type NamedArguments from Types
25
 * @psalm-import-type InjectableValue from Types
26
 */
27
28
/**
29
 * Assisted injection interceptor for #[Inject] attributed parameter
30
 *
31
 * @psalm-import-type NamedArguments from Types
32
 */
33
final class AssistedInjectInterceptor implements MethodInterceptor
34
{
35
    /** @var InjectorInterface */
36
    private $injector;
37
38
    /** @var MethodInvocationProvider */
39
    private $methodInvocationProvider;
40
41
    public function __construct(InjectorInterface $injector, MethodInvocationProvider $methodInvocationProvider)
42
    {
43
        $this->injector = $injector;
44
        $this->methodInvocationProvider = $methodInvocationProvider;
45
    }
46
47
    /**
48
     * @return mixed
49
     */
50
    public function invoke(MethodInvocation $invocation)
51
    {
52
        $this->methodInvocationProvider->set($invocation);
53
        $params = $invocation->getMethod()->getParameters();
54
        $namedArguments = $this->getNamedArguments($invocation);
55
        foreach ($params as $param) {
56
            /** @var list<ReflectionAttribute> $inject */
57
            $inject = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF); // @phpstan-ignore-line
58
            /** @var list<ReflectionAttribute> $assisted */
59
            $assisted = $param->getAttributes(Assisted::class);
60
            if (isset($assisted[0]) || isset($inject[0])) {
61
                /** @psalm-suppress MixedAssignment */
62
                $namedArguments[$param->getName()] = $this->getDependency($param);
63
            }
64
        }
65
66
        $callable = [$invocation->getThis(), $invocation->getMethod()->getName()];
67
        assert(is_callable($callable));
68
69
        return call_user_func_array($callable, $namedArguments);
70
    }
71
72
    /**
73
     * @param MethodInvocation<object> $invocation
74
     *
75
     * @return array<string, mixed>
76
     */
77
    private function getNamedArguments(MethodInvocation $invocation): array
78
    {
79
        $args = $invocation->getArguments();
80
        $params = $invocation->getMethod()->getParameters();
81
        $namedParams = [];
82
        foreach ($params as $param) {
83
            $pos = $param->getPosition();
84
            if (isset($args[$pos])) {
85
                /** @psalm-suppress MixedAssignment */
86
                $namedParams[$param->getName()] = $args[$pos];
87
            }
88
        }
89
90
        return $namedParams;
91
    }
92
93
    /**
94
     * @return mixed
95
     */
96
    private function getDependency(ReflectionParameter $param)
97
    {
98
        $named = (string) $this->getName($param);
99
        $type = $param->getType();
100
        assert($type instanceof ReflectionNamedType || $type === null);
101
        $typeName = $type ? $type->getName() : '';
0 ignored issues
show
$type is of type null, thus it always evaluated to false.
Loading history...
102
        $interface = in_array($typeName, Argument::UNBOUND_TYPE) ? '' : $typeName;
103
104
        /** @var class-string $interface */
105
        return $this->injector->getInstance($interface, $named);
106
    }
107
108
    private function getName(ReflectionParameter $param): ?string
109
    {
110
        /** @var list<ReflectionAttribute> $nameds */
111
        $nameds = $param->getAttributes(Named::class);
112
        if (isset($nameds[0])) {
113
            $named = $nameds[0]->newInstance();
114
            assert($named instanceof Named);
115
116
            return $named->value;
117
        }
118
119
        if ($param->getAttributes(Inject::class)) {
120
            return null;
121
        }
122
123
        return $this->getCustomInject($param);
124
    }
125
126
    /**
127
     * @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...
128
     */
129
    private function getCustomInject(ReflectionParameter $param): ?string
130
    {
131
        /** @var list<ReflectionAttribute> $injects */
132
        $injects = $param->getAttributes(InjectInterface::class, ReflectionAttribute::IS_INSTANCEOF);
133
        if (! $injects) {
0 ignored issues
show
$injects is of type Ray\Di\list, thus it always evaluated to true.
Loading history...
134
            return null;
135
        }
136
137
        $inject = $injects[0]->newInstance();
138
        assert($inject instanceof InjectInterface);
139
140
        return get_class($inject);
141
    }
142
}
143