Passed
Push — master ( ade161...a5ce50 )
by Divine Niiquaye
02:26
created

ResolverChain::getContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHP Invoker.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace DivineNii\Invoker;
19
20
use Psr\Container\ContainerInterface;
21
use Psr\Container\NotFoundExceptionInterface;
22
use ReflectionClass;
23
use ReflectionException;
24
use ReflectionFunctionAbstract;
25
use ReflectionNamedType;
26
use ReflectionParameter;
27
use ReflectionType;
28
29
class ResolverChain
30
{
31
    /** @var null|ContainerInterface */
32
    private $container;
33
34 47
    public function __construct(?ContainerInterface $container = null)
35
    {
36 47
        $this->container = $container;
37 47
    }
38
39
    /**
40
     * @return null|ContainerInterface
41
     */
42 1
    public function getContainer(): ?ContainerInterface
43
    {
44 1
        return $this->container;
45
    }
46
47
    /**
48
     * Tries to map an associative array (string-indexed) to the parameter names.
49
     *
50
     * E.g. `->call($callable, ['foo' => 'bar'])` will inject the string `'bar'`
51
     * in the parameter named `$foo`.
52
     *
53
     * Parameters that are not indexed by a string are ignored.
54
     *
55
     * @param ReflectionFunctionAbstract $reflection
56
     * @param array<int|string,mixed>    $providedParameters
57
     * @param array<int|string,mixed>    $resolvedParameters
58
     *
59
     * @return array<int|mixed>
60
     */
61 20
    public function resolveAssociativeArray(
62
        ReflectionFunctionAbstract $reflection,
63
        array $providedParameters,
64
        array $resolvedParameters
65
    ): array {
66 20
        $parameters = $reflection->getParameters();
67
68
        // Skip parameters already resolved
69 20
        if (!empty($resolvedParameters)) {
70 3
            $parameters = \array_diff_key($parameters, $resolvedParameters);
71
        }
72
73 20
        foreach ($parameters as $index => $parameter) {
74 20
            $name  = $parameter->name;
75
76
            // Inject entries from a DI container using the parameter names.
77 20
            if ($name && (null !== $this->container && $this->container->has($name))) {
78 2
                $resolvedParameters[$index] = $this->container->get($name);
79
80 2
                continue;
81
            }
82
83 18
            if (\array_key_exists($name, $providedParameters)) {
84 9
                $resolvedParameters[$index] = $providedParameters[$name];
85
            }
86
        }
87
88 20
        return $resolvedParameters;
89
    }
90
91
    /**
92
     * Simply returns all the values of the $providedParameters array that are
93
     * indexed by the parameter position (i.e. a number).
94
     *
95
     * E.g. `->call($callable, ['foo', 'bar'])` will simply resolve the parameters
96
     * to `['foo', 'bar']`.
97
     *
98
     * Parameters that are not indexed by a number (i.e. parameter position)
99
     * will be ignored.
100
     *
101
     * @param ReflectionFunctionAbstract $reflection
102
     * @param array<int|string,mixed>    $providedParameters
103
     * @param array<int|string,mixed>    $resolvedParameters
104
     *
105
     * @return array<int|mixed>
106
     */
107 39
    public function resolveNumericArray(
108
        ReflectionFunctionAbstract $reflection,
109
        array $providedParameters,
110
        array $resolvedParameters
111
    ): array {
112
        // Skip parameters already resolved
113 39
        $providedParameters = \array_diff_key($providedParameters, $resolvedParameters) ?? $providedParameters;
114
115 39
        foreach ($providedParameters as $key => $value) {
116 18
            if (\is_int($key)) {
117 8
                $resolvedParameters[$key] = $value;
118
            }
119
        }
120
121 39
        return $resolvedParameters;
122
    }
123
124
    /**
125
     * Finds the default value for a parameter, *if it exists*.
126
     *
127
     * @param ReflectionFunctionAbstract $reflection
128
     * @param array<int|string,mixed>    $providedParameters
129
     * @param array<int|string,mixed>    $resolvedParameters
130
     *
131
     * @return array<int|mixed>
132
     */
133 15
    public function resolveDefaultValue(
134
        ReflectionFunctionAbstract $reflection,
135
        array $providedParameters,
136
        array $resolvedParameters
137
    ): array {
138 15
        $parameters = $reflection->getParameters();
139
140
        // Skip parameters already resolved
141 15
        if (!empty($resolvedParameters)) {
142 8
            $parameters = \array_diff_key($parameters, $resolvedParameters);
143
        }
144
145 15
        foreach ($parameters as $index => $parameter) {
146
            /** @var ReflectionParameter $parameter */
147 15
            if ($parameter->isOptional() || $parameter->isDefaultValueAvailable()) {
148
                try {
149 7
                    $resolvedParameters[$index] = $parameter->getDefaultValue();
150 1
                } catch (ReflectionException $e) {
151
                    // Can't get default values from PHP internal classes and functions
152
                }
153
            }
154
        }
155
156 15
        return $resolvedParameters;
157
    }
158
159
    /**
160
     * Inject entries using type-hints.
161
     *
162
     * Tries to match type-hints with the parameters provided.
163
     *
164
     * @param ReflectionFunctionAbstract $reflection
165
     * @param array<int|string,mixed>    $providedParameters
166
     * @param array<int|string,mixed>    $resolvedParameters
167
     *
168
     * @return array<int|mixed>
169
     */
170 22
    public function resolveTypeHint(
171
        ReflectionFunctionAbstract $reflection,
172
        array $providedParameters,
173
        array $resolvedParameters
174
    ): array {
175 22
        $parameters = $reflection->getParameters();
176
177
        // Skip parameters already resolved
178 22
        if (!empty($resolvedParameters)) {
179 3
            $parameters = \array_diff_key($parameters, $resolvedParameters);
180
        }
181
182 22
        foreach ($parameters as $index => $parameter) {
183 22
            $parameterType = $parameter->getType();
184
185 22
            if (!$parameterType instanceof ReflectionType) {
186
                // No type
187 11
                continue;
188
            }
189
190 11
            if ($parameterType->isBuiltin()) {
191
                // Primitive types are not supported
192 2
                continue;
193
            }
194
195
            // @codeCoverageIgnoreStart
196
            if (!$parameterType instanceof ReflectionNamedType) {
197
                // Union types are not supported
198
                continue;
199
            }
200
            // @codeCoverageIgnoreEnd
201
202 10
            $parameterClass = $parameterType->getName();
203
204
            // Inject entries from a DI container using the type-hints.
205 10
            if (null !== $this->container && $this->container->has($parameterClass)) {
206 1
                $resolvedParameters[$index] = $this->container->get($parameterClass);
207
208 1
                continue;
209
            }
210
211 9
            if (\array_key_exists($parameterClass, $providedParameters)) {
212 1
                $resolvedParameters[$index] = $providedParameters[$parameterClass];
213
            }
214
        }
215
216 22
        return $resolvedParameters;
217
    }
218
219
    /**
220
     * Inject or create a class instance from a DI container or return existing instance
221
     * from $providedParameters using the type-hints.
222
     *
223
     * @param ReflectionFunctionAbstract $reflection
224
     * @param array<int|string,mixed>    $providedParameters
225
     * @param array<int|string,mixed>    $resolvedParameters
226
     *
227
     * @return array<int|mixed>
228
     */
229 9
    public function resolveParameterContainer(
230
        ReflectionFunctionAbstract $reflection,
231
        array $providedParameters,
232
        array $resolvedParameters
233
    ): array {
234 9
        $parameters = $reflection->getParameters();
235
236
        // Skip parameters already resolved
237 9
        if (!empty($resolvedParameters)) {
238 2
            $parameters = \array_diff_key($parameters, $resolvedParameters);
239
        }
240
241 9
        foreach ($parameters as $index => $parameter) {
242 9
            $parameterClass = $parameter->getClass();
243
244 9
            if (!$parameterClass instanceof ReflectionClass) {
245 3
                continue;
246
            }
247
248
            // Inject entries from a DI container using the type-hints.
249 6
            if (null !== $this->container) {
250
                try {
251 3
                    $resolvedParameters[$index] = $this->container->get($parameterClass->name);
252
253 2
                    continue;
254 1
                } catch (NotFoundExceptionInterface $e) {
255
                    // We need no exception thrown here
256
                }
257
            }
258
259
            // If an instance is detected
260 4
            foreach ($providedParameters as $key => $value) {
261 1
                if (\is_a($value, $parameterClass->name, true)) {
262 1
                    $resolvedParameters[$index] = $providedParameters[$key];
263
264 1
                    continue;
265
                }
266
            }
267
268 4
            if ($parameterClass->isInstantiable()) {
269 4
                $resolvedParameters[$index] = $parameterClass->newInstance();
270
            }
271
        }
272
273 9
        return $resolvedParameters;
274
    }
275
}
276