Test Failed
Pull Request — master (#37)
by Divine Niiquaye
15:08
created

Resolver   F

Complexity

Total Complexity 184

Size/Duplication

Total Lines 619
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 282
c 7
b 0
f 0
dl 0
loc 619
rs 2
wmc 184

17 Methods

Rating   Name   Duplication   Size   Complexity  
C autowireArguments() 0 44 13
B getTypes() 0 35 11
B getParameterDefaultValue() 0 21 7
A __construct() 0 4 1
D autowireService() 0 56 25
B get() 0 25 7
F resolveServiceSubscriber() 0 39 20
D resolve() 0 50 22
A createMethod() 0 3 1
A setStrictAutowiring() 0 3 1
C resolveCallable() 0 40 16
A getBuilder() 0 3 1
D autowireArgument() 0 74 25
B resolveClass() 0 25 8
B resolveReference() 0 23 7
A getDefinitionClass() 0 15 5
C resolveArguments() 0 27 14

How to fix   Complexity   

Complex Class

Complex classes like Resolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Resolver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.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 Rade\DI;
19
20
use Nette\Utils\{Callback, Reflection};
21
use PhpParser\BuilderFactory;
22
use PhpParser\Node\{Expr, Stmt, Scalar};
23
use Rade\DI\Exceptions\{ContainerResolutionException, NotFoundServiceException};
24
use Symfony\Contracts\Service\{ServiceProviderInterface, ServiceSubscriberInterface};
25
26
/**
27
 * Class Resolver.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
class Resolver
32
{
33
    private AbstractContainer $container;
34
    private ?BuilderFactory $builder;
35
    private bool $strict = true;
36
37
    /** @var array<string,\PhpParser\Node> */
38
    private array $literalCache = [];
39
40
    public function __construct(AbstractContainer $container, BuilderFactory $builder = null)
41
    {
42
        $this->builder = $builder;
43
        $this->container = $container;
44
    }
45
46
    /**
47
     * The method name generated for a service definition.
48
     */
49
    public static function createMethod(string $id): string
50
    {
51
        return 'get' . \str_replace(['.', '_', '\\'], '', \ucwords($id, '._'));
52
    }
53
54
    /**
55
     * If true, exception will be thrown on resolvable services with are not typed.
56
     */
57
    public function setStrictAutowiring(bool $boolean = true): void
58
    {
59
        $this->strict = $boolean;
60
    }
61
62
    /**
63
     * @param mixed $definition
64
     */
65
    public static function autowireService($definition, bool $allTypes = false, AbstractContainer $container = null): array
66
    {
67
        $types = $autowired = [];
68
69
        if (\is_callable($definition)) {
70
            $types = \array_filter(self::getTypes(Callback::toReflection($definition)), fn (string $v) => \class_exists($v) || \interface_exists($v) || $allTypes);
71
        } elseif (\is_object($definition)) {
72
            if ($definition instanceof \stdClass) {
73
                return $allTypes ? ['object'] : $types;
74
            }
75
76
            $types[] = \get_class($definition);
77
        } elseif (\is_string($definition)) {
78
            if (!(\class_exists($definition) || \interface_exists($definition))) {
79
                return $allTypes ? ['string'] : [];
80
            }
81
82
            $types[] = $definition;
83
        } elseif (\is_array($definition)) {
84
            if (null !== $container && 2 === \count($definition, \COUNT_RECURSIVE)) {
85
                if ($definition[0] instanceof Definitions\Reference) {
86
                    $def = $container->definition((string) $definition[0]);
87
                } elseif ($definition[0] instanceof Expr\BinaryOp\Coalesce) {
88
                    $def = $container->definition($definition[0]->left->dim->value);
89
                }
90
91
                if (isset($def)) {
92
                    if ($def instanceof Definitions\DefinitionInterface) {
93
                        $class = self::getDefinitionClass($def);
94
95
                        if (null === $class) {
96
                            return [];
97
                        }
98
                    }
99
                    $types = self::getTypes(new \ReflectionMethod($class ?? $def, $definition[1]));
100
                    goto resolve_types;
101
                }
102
            }
103
104
            return $allTypes ? ['array'] : [];
105
        }
106
107
        resolve_types:
108
        foreach ($types as $type) {
109
            $autowired[] = $type;
110
111
            foreach (\class_implements($type) ?: [] as $interface) {
112
                $autowired[] = $interface;
113
            }
114
115
            foreach (\class_parents($type) ?: [] as $parent) {
116
                $autowired[] = $parent;
117
            }
118
        }
119
120
        return $autowired;
121
    }
122
123
    /**
124
     * Resolves arguments for callable.
125
     *
126
     * @param array<int|string,mixed> $args
127
     *
128
     * @return array<int,mixed>
129
     */
130
    public function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
131
    {
132
        $resolvedParameters = [];
133
        $nullValuesFound = 0;
134
        $args = $this->resolveArguments($args); // Resolves provided arguments.
135
136
        foreach ($function->getParameters() as $offset => $parameter) {
137
            $position = 0 === $nullValuesFound ? $offset : $parameter->name;
138
            $resolved = $args[$offset] ?? $args[$parameter->name] ?? null;
139
            $types = self::getTypes($parameter);
140
141
            if (\PHP_VERSION_ID >= 80100 && (\count($types) > 1 && \is_subclass_of($enumType = $types[0], \BackedEnum::class))) {
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
142
                if (null === ($resolved = $resolved ?? $providedParameters[$enumType] ?? null)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $providedParameters seems to never exist and therefore isset should always be false.
Loading history...
143
                    throw new ContainerResolutionException(\sprintf('Missing parameter %s.', Reflection::toString($parameter)));
144
                }
145
                $resolvedParameters[$position] = $enumType::from($resolved);
146
147
                continue;
148
            }
149
150
            if (null === ($resolved = $resolved ?? $this->autowireArgument($parameter, $types, $args))) {
151
                if ($parameter->isDefaultValueAvailable()) {
152
                    if (\PHP_MAJOR_VERSION < 8) {
153
                        $resolvedParameters[$position] = Reflection::getParameterDefaultValue($parameter);
154
                    } else {
155
                        ++$nullValuesFound;
156
                    }
157
                } elseif (!$parameter->isVariadic()) {
158
                    $resolvedParameters[$position] = self::getParameterDefaultValue($parameter, $types);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $resolvedParameters[$position] is correct as self::getParameterDefaultValue($parameter, $types) targeting Rade\DI\Resolver::getParameterDefaultValue() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
159
                }
160
161
                continue;
162
            }
163
164
            if ($parameter->isVariadic() && \is_array($resolved)) {
165
                $resolvedParameters = \array_merge($resolvedParameters, $resolved);
166
167
                continue;
168
            }
169
170
            $resolvedParameters[$position] = $resolved;
171
        }
172
173
        return $resolvedParameters;
174
    }
175
176
    /**
177
     * Resolve a service definition, class string, invocable object or callable
178
     * using autowiring.
179
     *
180
     * @param string|callable|object  $callback
181
     * @param array<int|string,mixed> $args
182
     *
183
     * @throws ContainerResolutionException|\ReflectionException if unresolvable
184
     *
185
     * @return mixed
186
     */
187
    public function resolve($callback, array $args = [])
188
    {
189
        if ($callback instanceof Definitions\Statement) {
190
            if (Services\ServiceLocator::class == ($value = $callback->getValue())) {
191
                $services = [];
192
                $args = \array_merge($args, $callback->getArguments());
193
194
                foreach ($args as $name => $service) {
195
                    $services += $this->resolveServiceSubscriber($name, (string) $service);
196
                }
197
                $resolved = null === $this->builder ? new Services\ServiceLocator($services) : $this->builder->new('\\' . Services\ServiceLocator::class, [$services]);
198
            } else {
199
                $resolved = $this->resolve($value, $callback->getArguments() + $args);
200
201
                if ($callback->isClosureWrappable()) {
202
                    $resolved = null === $this->builder ? fn () => $resolved : new Expr\ArrowFunction(['expr' => $resolved]);
203
                }
204
            }
205
        } elseif ($callback instanceof Definitions\Reference) {
206
            $resolved = $this->resolveReference((string) $callback);
207
208
            if (\is_callable($resolved) || (\is_array($resolved) && 2 === \count($resolved, \COUNT_RECURSIVE))) {
209
                $resolved = $this->resolveCallable($resolved, $args);
210
            } else {
211
                $callback = $resolved;
212
            }
213
        } elseif ($callback instanceof Definitions\ValueDefinition) {
214
            $resolved = $callback->getEntity();
215
        } elseif ($callback instanceof Definitions\TaggedLocator) {
216
            $resolved = $this->resolve($callback->resolve($this->container));
217
        } elseif ($callback instanceof Builder\PhpLiteral) {
218
            $expression = $this->literalCache[\spl_object_id($callback)] ??= $callback->resolve($this)[0];
219
            $resolved = $expression instanceof Stmt\Expression ? $expression->expr : $expression;
220
        } elseif (\is_string($callback)) {
221
            if (\str_contains($callback, '%')) {
222
                $callback = $this->container->parameter($callback);
223
            }
224
225
            if (\class_exists($callback)) {
226
                return $this->resolveClass($callback, $args);
227
            }
228
229
            if (\is_callable($callback)) {
230
                $resolved = $this->resolveCallable($callback, $args);
231
            }
232
        } elseif (\is_callable($callback) || \is_array($callback)) {
233
            $resolved = $this->resolveCallable($callback, $args);
0 ignored issues
show
Bug introduced by
It seems like $callback can also be of type object; however, parameter $callback of Rade\DI\Resolver::resolveCallable() does only seem to accept array<integer,mixed>|callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

233
            $resolved = $this->resolveCallable(/** @scrutinizer ignore-type */ $callback, $args);
Loading history...
234
        }
235
236
        return $resolved ?? (null === $this->builder ? $callback : $this->builder->val($callback));
237
    }
238
239
    /**
240
     * Resolves callables and array like callables.
241
     *
242
     * @param callable|array<int,mixed> $callback
243
     * @param array<int|string,mixed>   $arguments
244
     *
245
     * @throws \ReflectionException if $callback is not a real callable
246
     *
247
     * @return mixed
248
     */
249
    public function resolveCallable($callback, array $arguments = [])
250
    {
251
        if (\is_array($callback)) {
252
            if (2 === \count($callback, \COUNT_RECURSIVE) && \is_string($callback[1])) {
253
                $callback[0] = $this->resolve($callback[0]);
254
255
                if ($callback[0] instanceof Expr\BinaryOp\Coalesce) {
256
                    $class = self::getDefinitionClass($this->container->definition($callback[0]->left->dim->value));
257
258
                    if (null !== $class) {
259
                        $type = [$class, $callback[1]];
260
                    }
261
                } elseif ($callback[0] instanceof Expr\New_) {
262
                    $type = [(string) $callback[0]->class, $callback[1]];
263
                }
264
265
                if (isset($type) || \is_callable($callback)) {
266
                    goto create_callable;
267
                }
268
            }
269
270
            $callback = $this->resolveArguments($callback);
271
272
            return null === $this->builder ? $callback : $this->builder->val($callback);
273
        }
274
275
        create_callable:
276
        $args = $this->autowireArguments($ref = Callback::toReflection($type ?? $callback), $arguments);
277
278
        if ($ref instanceof \ReflectionFunction) {
279
            return null === $this->builder ? $ref->invokeArgs($args) : $this->builder->funcCall($callback, $args);
0 ignored issues
show
Bug introduced by
$callback of type array<integer,mixed>|callable is incompatible with the type PhpParser\Node\Expr|PhpParser\Node\Name|string expected by parameter $name of PhpParser\BuilderFactory::funcCall(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

279
            return null === $this->builder ? $ref->invokeArgs($args) : $this->builder->funcCall(/** @scrutinizer ignore-type */ $callback, $args);
Loading history...
280
        }
281
282
        if ($ref->isStatic()) {
283
            $className = \is_array($callback) ? $callback[0] : $ref->getDeclaringClass()->getName();
284
285
            return null === $this->builder ? $ref->invokeArgs(null, $args) : $this->builder->staticCall($className, $ref->getName(), $args);
286
        }
287
288
        return null === $this->builder ? $callback(...$args) : $this->builder->methodCall($callback[0], $ref->getName(), $args);
289
    }
290
291
    /**
292
     * @param array<int|string,mixed> $args
293
     *
294
     * @throws ContainerResolutionException|\ReflectionException if class string unresolvable
295
     */
296
    public function resolveClass(string $class, array $args = []): object
297
    {
298
        /** @var class-string $class */
299
        $reflection = new \ReflectionClass($class);
300
301
        if ($reflection->isAbstract() || !$reflection->isInstantiable()) {
302
            throw new ContainerResolutionException(\sprintf('Class %s is an abstract type or instantiable.', $class));
303
        }
304
305
        if (null === $constructor = $reflection->getConstructor()) {
306
            if (!empty($args)) {
307
                throw new ContainerResolutionException(\sprintf('Unable to pass arguments, class "%s" has no constructor.', $class));
308
            }
309
310
            $service = null === $this->builder ? $reflection->newInstanceWithoutConstructor() : $this->builder->new($class);
311
        } else {
312
            $args = $this->autowireArguments($constructor, $args);
313
            $service = null === $this->builder ? $reflection->newInstanceArgs($args) : $this->builder->new($class, $args);
314
        }
315
316
        if ($reflection->implementsInterface(Injector\InjectableInterface::class)) {
317
            return Injector\Injectable::getProperties($this, $service, $reflection);
318
        }
319
320
        return $service;
321
    }
322
323
    /**
324
     * @param array<int|string,mixed> $arguments
325
     *
326
     * @return array<int|string,mixed>
327
     */
328
    public function resolveArguments(array $arguments = []): array
329
    {
330
        foreach ($arguments as $key => $value) {
331
            if ($value instanceof \stdClass) {
332
                $resolved = null === $this->builder ? $value : new Expr\Cast\Object_($this->builder->val($this->resolveArguments((array) $value)));
333
            } elseif (\is_array($value)) {
334
                $resolved = $this->resolveArguments($value);
335
            } elseif (\is_int($value)) {
336
                $resolved = null === $this->builder ? $value : new Scalar\LNumber($value);
337
            } elseif (\is_float($value)) {
338
                $resolved = null === $this->builder ? (int) $value : new Scalar\DNumber($value);
339
            } elseif (\is_numeric($value)) {
340
                $resolved = null === $this->builder ? (int) $value : Scalar\LNumber::fromString($value);
341
            } elseif (\is_string($value)) {
342
                if (\str_contains($value, '%')) {
343
                    $value = $this->container->parameter($value);
344
                }
345
346
                $resolved = null === $this->builder ? $value : $this->builder->val($value);
347
            } else {
348
                $resolved = $this->resolve($value);
349
            }
350
351
            $arguments[$key] = $resolved;
352
        }
353
354
        return $arguments;
355
    }
356
357
    /**
358
     * Resolves service by type.
359
     *
360
     * @param string $id A class or an interface name
361
     *
362
     * @return mixed
363
     */
364
    public function get(string $id, bool $single = false)
365
    {
366
        if (\is_subclass_of($id, ServiceSubscriberInterface::class)) {
367
            static $services = [];
368
369
            foreach ($id::getSubscribedServices() as $name => $service) {
370
                $services += $this->resolveServiceSubscriber($name, $service);
371
            }
372
373
            if (null === $builder = $this->builder) {
374
                return new Services\ServiceLocator($services);
375
            }
376
377
            return $builder->new('\\' . Services\ServiceLocator::class, [$services]);
378
        }
379
380
        if (!$this->strict) {
381
            return $this->container->get($id, $single ? $this->container::EXCEPTION_ON_MULTIPLE_SERVICE : $this->container::IGNORE_MULTIPLE_SERVICE);
382
        }
383
384
        if ($this->container->typed($id)) {
385
            return $this->container->autowired($id, $single);
386
        }
387
388
        throw new NotFoundServiceException(\sprintf('Service of type "%s" not found. Check class name because it cannot be found.', $id));
389
    }
390
391
    /**
392
     * Gets the PHP's parser builder.
393
     */
394
    public function getBuilder(): ?BuilderFactory
395
    {
396
        return $this->builder;
397
    }
398
399
    /**
400
     * @return mixed
401
     */
402
    private function resolveReference(string $reference)
403
    {
404
        if ('?' === $reference[0]) {
405
            $invalidBehavior = $this->container::EXCEPTION_ON_MULTIPLE_SERVICE;
406
            $reference = \substr($reference, 1);
407
408
            if ($arrayLike = \str_ends_with('[]', $reference)) {
409
                $reference = \substr($reference, 0, -2);
410
                $invalidBehavior = $this->container::IGNORE_MULTIPLE_SERVICE;
411
            }
412
413
            if ($this->container->has($reference) || $this->container->typed($reference)) {
414
                return $this->container->get($reference, $invalidBehavior);
415
            }
416
417
            return $arrayLike ? [] : null;
418
        }
419
420
        if ('[]' === \substr($reference, -2)) {
421
            return $this->container->get(\substr($reference, 0, -2), $this->container::IGNORE_MULTIPLE_SERVICE);
422
        }
423
424
        return $this->container->get($reference);
425
    }
426
427
    /**
428
     * Resolves services for ServiceLocator.
429
     *
430
     * @param int|string $id
431
     *
432
     * @return (\Closure|array|mixed|null)[]
433
     */
434
    private function resolveServiceSubscriber($id, string $value): array
435
    {
436
        if ('?' === $value[0]) {
437
            $arrayLike = \str_ends_with($value = \substr($value, 1), '[]');
438
439
            if (\is_int($id)) {
440
                $id = $arrayLike ? \substr($value, 0, -2) : $value;
441
            }
442
443
            return ($this->container->has($id) || $this->container->typed($id)) ? $this->resolveServiceSubscriber($id, $value) : [$id => $arrayLike ? [] : null];
444
        }
445
446
        $service = function () use ($value) {
447
            if ('[]' === \substr($value, -2)) {
448
                $service = $this->container->get(\substr($value, 0, -2), $this->container::IGNORE_MULTIPLE_SERVICE);
449
450
                return \is_array($service) ? $service : [$service];
451
            }
452
453
            return $this->container->get($value);
454
        };
455
456
        if (null !== $this->builder) {
457
            if ('[]' === \substr($value, -2)) {
458
                $returnType = 'array';
459
            } if ($this->container->has($value)) {
460
                $returnType = $this->container->definition($value)->getTypes()[0] ?? (
461
                    \class_exists($value) || \interface_exists($value)
462
                    ? $value
463
                    : (!\is_int($id) && (\class_exists($id) || \interface_exists($id)) ? $id : null)
464
                );
465
            } elseif (\class_exists($value) || \interface_exists($value)) {
466
                $returnType = $value;
467
            }
468
469
            $service = new Expr\ArrowFunction(['expr' => $this->builder->val($service()), 'returnType' => $returnType ?? null]);
470
        }
471
472
        return [\is_int($id) ? \rtrim($value, '[]') : $id => $service];
473
    }
474
475
    /**
476
     * Resolves missing argument using autowiring.
477
     *
478
     * @param array<int|string,mixed> $providedParameters
479
     * @param array<int,string>       $types
480
     *
481
     * @throws ContainerResolutionException
482
     *
483
     * @return mixed
484
     */
485
    private function autowireArgument(\ReflectionParameter $parameter, array $types, array $providedParameters)
486
    {
487
        foreach ($types as $typeName) {
488
            if (!Reflection::isBuiltinType($typeName)) {
489
                try {
490
                    return $providedParameters[$typeName] ?? $this->get($typeName, !$parameter->isVariadic());
491
                } catch (NotFoundServiceException $e) {
492
                    // Ignore this exception ...
493
                } catch (ContainerResolutionException $e) {
494
                    $errorException = new ContainerResolutionException(\sprintf("{$e->getMessage()} (needed by %s)", Reflection::toString($parameter)));
495
                }
496
497
                if (
498
                    ServiceProviderInterface::class === $typeName &&
499
                    null !== $class = $parameter->getDeclaringClass()
500
                ) {
501
                    if (!$class->implementsInterface(ServiceSubscriberInterface::class)) {
502
                        throw new ContainerResolutionException(\sprintf(
503
                            'Service of type %s needs parent class %s to implement %s.',
504
                            $typeName,
505
                            $class->getName(),
506
                            ServiceSubscriberInterface::class
507
                        ));
508
                    }
509
510
                    return $this->get($class->getName());
511
                }
512
            }
513
514
            if (\PHP_MAJOR_VERSION >= 8 && $attributes = $parameter->getAttributes()) {
515
                foreach ($attributes as $attribute) {
516
                    if (Attribute\Inject::class === $attribute->getName()) {
517
                        if (null === $attrName = $attribute->getArguments()[0] ?? null) {
518
                            throw new ContainerResolutionException(\sprintf('Using the Inject attribute on parameter %s requires a value to be set.', $parameter->getName()));
519
                        }
520
521
                        if ($arrayLike = \str_ends_with($attrName, '[]')) {
522
                            $attrName = \substr($attrName, 0, -2);
523
                        }
524
525
                        try {
526
                            return $this->get($attrName, !$arrayLike);
527
                        } catch (NotFoundServiceException $e) {
528
                            // Ignore this exception ...
529
                        }
530
                    }
531
                }
532
            }
533
534
            if (
535
                ($method = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod
536
                && \preg_match('#@param[ \t]+([\w\\\\]+)(?:\[\])?[ \t]+\$' . $parameter->name . '#', (string) $method->getDocComment(), $m)
537
                && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
538
                && (\class_exists($itemType) || \interface_exists($itemType))
539
            ) {
540
                try {
541
                    if (\in_array($typeName, ['array', 'iterable'], true)) {
542
                        return $this->get($itemType);
543
                    }
544
545
                    if ('object' === $typeName || \is_subclass_of($itemType, $typeName)) {
546
                        return $this->get($itemType, true);
547
                    }
548
                } catch (NotFoundServiceException $e) {
549
                    // Ignore this exception ...
550
                }
551
            }
552
553
            if (isset($errorException)) {
554
                throw $errorException;
555
            }
556
        }
557
558
        return null;
559
    }
560
561
    /**
562
     * Returns an associated type to the given parameter if available.
563
     *
564
     * @param \ReflectionParameter|\ReflectionFunctionAbstract $reflection
565
     *
566
     * @return array<int,string>
567
     */
568
    private static function getTypes(\Reflector $reflection): array
569
    {
570
        if ($reflection instanceof \ReflectionParameter) {
571
            $type = $reflection->getType();
572
        } elseif ($reflection instanceof \ReflectionFunctionAbstract) {
573
            $type = $reflection->getReturnType() ?? (\PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
0 ignored issues
show
Bug introduced by
The method getTentativeReturnType() does not exist on ReflectionFunctionAbstract. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

573
            $type = $reflection->getReturnType() ?? (\PHP_VERSION_ID >= 80100 ? $reflection->/** @scrutinizer ignore-call */ getTentativeReturnType() : null);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
574
        }
575
576
        if (!isset($type)) {
577
            return [];
578
        }
579
580
        $resolver = static function (\ReflectionNamedType $rName) use ($reflection): string {
581
            $function = $reflection instanceof \ReflectionParameter ? $reflection->getDeclaringFunction() : $reflection;
582
583
            if ($function instanceof \ReflectionMethod) {
584
                $lcName = \strtolower($rName->getName());
585
586
                if ('self' === $lcName || 'static' === $lcName) {
587
                    return $function->getDeclaringClass()->name;
588
                }
589
590
                if ('parent' === $lcName) {
591
                    return $function->getDeclaringClass()->getParentClass()->name;
592
                }
593
            }
594
595
            return $rName->getName();
596
        };
597
598
        if (!$type instanceof \ReflectionNamedType) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
599
            return \array_map($resolver, $type->getTypes());
600
        }
601
602
        return [$resolver($type)];
603
    }
604
605
    private static function getDefinitionClass(Definitions\DefinitionInterface $def): ?string
606
    {
607
        if (!\class_exists($class = $def->getEntity())) {
608
            if ($def instanceof Definitions\TypedDefinitionInterface) {
609
                foreach ($def->getTypes() as $typed) {
610
                    if (\class_exists($typed)) {
611
                        return $typed;
612
                    }
613
                }
614
            }
615
616
            return null;
617
        }
618
619
        return $class;
620
    }
621
622
    /**
623
     * Get the parameter's allowed null else error.
624
     *
625
     * @throws \ReflectionException|ContainerResolutionException
626
     *
627
     * @return null|void
628
     */
629
    private static function getParameterDefaultValue(\ReflectionParameter $parameter, array $types)
630
    {
631
        if ($parameter->isOptional() || $parameter->allowsNull()) {
632
            return null;
633
        }
634
635
        $errorDescription = 'Parameter ' . Reflection::toString($parameter);
636
637
        if ('' === ($typedHint = \implode('|', $types))) {
638
            $errorDescription .= ' has no type hint or default value.';
639
        } elseif (\str_contains($typedHint, '|')) {
640
            $errorDescription .= ' has multiple type-hints ("' . $typedHint . '").';
641
        } elseif (\class_exists($typedHint)) {
642
            $errorDescription .= ' has an unresolved class-based type-hint ("' . $typedHint . '").';
643
        } elseif (\interface_exists($typedHint)) {
644
            $errorDescription .= ' has an unresolved interface-based type-hint ("' . $typedHint . '").';
645
        } else {
646
            $errorDescription .= ' has a type-hint ("' . $typedHint . '") that cannot be resolved, perhaps a you forgot to set it up?';
647
        }
648
649
        throw new ContainerResolutionException($errorDescription);
650
    }
651
}
652