Test Failed
Pull Request — master (#37)
by Divine Niiquaye
13:18
created

Resolver::resolveArguments()   C

Complexity

Conditions 14
Paths 15

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 19
nc 15
nop 1
dl 0
loc 27
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 PhpParser\Node\Scalar\String_;
24
use Rade\DI\Exceptions\{ContainerResolutionException, NotFoundServiceException};
25
use Symfony\Contracts\Service\{ServiceProviderInterface, ServiceSubscriberInterface};
26
27
/**
28
 * Class Resolver.
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
class Resolver
33
{
34
    private AbstractContainer $container;
35
    private ?BuilderFactory $builder;
36
    private bool $strict = true;
37
38
    /** @var array<string,\PhpParser\Node> */
39
    private array $literalCache = [];
40
41
    public function __construct(AbstractContainer $container, BuilderFactory $builder = null)
42
    {
43
        $this->builder = $builder;
44
        $this->container = $container;
45
    }
46
47
    /**
48
     * The method name generated for a service definition.
49
     */
50
    public static function createMethod(string $id): string
51
    {
52
        return 'get' . \str_replace(['.', '_', '\\'], '', \ucwords($id, '._'));
53
    }
54
55
    /**
56
     * If true, exception will be thrown on resolvable services with are not typed.
57
     */
58
    public function setStrictAutowiring(bool $boolean = true): void
59
    {
60
        $this->strict = $boolean;
61
    }
62
63
    /**
64
     * @param mixed $definition
65
     */
66
    public static function autowireService($definition, bool $allTypes = false, AbstractContainer $container = null): array
67
    {
68
        $types = $autowired = [];
69
70
        if (\is_callable($definition)) {
71
            $types = \array_filter(self::getTypes(Callback::toReflection($definition)), fn (string $v) => \class_exists($v) || \interface_exists($v) || $allTypes);
72
        } elseif (\is_object($definition)) {
73
            if ($definition instanceof \stdClass) {
74
                return $allTypes ? ['object'] : $types;
75
            }
76
77
            $types[] = \get_class($definition);
78
        } elseif (\is_string($definition)) {
79
            if (!(\class_exists($definition) || \interface_exists($definition))) {
80
                return $allTypes ? ['string'] : [];
81
            }
82
83
            $types[] = $definition;
84
        } elseif (\is_array($definition)) {
85
            if (null !== $container && 2 === \count($definition, \COUNT_RECURSIVE)) {
86
                if ($definition[0] instanceof Definitions\Reference) {
87
                    $def = $container->definition((string) $definition[0]);
88
                } elseif ($definition[0] instanceof Expr\BinaryOp\Coalesce) {
89
                    $def = $container->definition($definition[0]->left->dim->value);
90
                }
91
92
                if (isset($def)) {
93
                    if ($def instanceof Definitions\DefinitionInterface) {
94
                        $class = self::getDefinitionClass($def);
95
96
                        if (null === $class) {
97
                            return [];
98
                        }
99
                    }
100
                    $types = self::getTypes(new \ReflectionMethod($class ?? $def, $definition[1]));
101
                    goto resolve_types;
102
                }
103
            }
104
105
            return $allTypes ? ['array'] : [];
106
        }
107
108
        resolve_types:
109
        foreach ($types as $type) {
110
            $autowired[] = $type;
111
112
            foreach (\class_implements($type) ?: [] as $interface) {
113
                $autowired[] = $interface;
114
            }
115
116
            foreach (\class_parents($type) ?: [] as $parent) {
117
                $autowired[] = $parent;
118
            }
119
        }
120
121
        return $autowired;
122
    }
123
124
    /**
125
     * Resolves arguments for callable.
126
     *
127
     * @param array<int|string,mixed> $args
128
     *
129
     * @return array<int,mixed>
130
     */
131
    public function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
132
    {
133
        $resolvedParameters = [];
134
        $nullValuesFound = 0;
135
        $args = $this->resolveArguments($args); // Resolves provided arguments.
136
137
        foreach ($function->getParameters() as $offset => $parameter) {
138
            $position = 0 === $nullValuesFound ? $offset : $parameter->name;
139
            $resolved = $args[$offset] ?? $args[$parameter->name] ?? null;
140
            $types = self::getTypes($parameter) ?: ['null'];
141
142
            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...
143
                if (null === $resolved = ($resolved ?? $args[$enumType] ?? null)) {
144
                    throw new ContainerResolutionException(\sprintf('Missing value for enum parameter %s.', Reflection::toString($parameter)));
145
                }
146
147
                try {
148
                    $resolvedParameters[$position] = $enumType::from($resolved);
149
                } catch (\ValueError $e) {
150
                    throw new ContainerResolutionException(\sprintf('The "%s" value could not be resolved for enum parameter %s.', $resolved, Reflection::toString($parameter)), 0, $e);
151
                }
152
                continue;
153
            }
154
155
            if (null === ($resolved = $resolved ?? $this->autowireArgument($parameter, $types, $args))) {
156
                if ($parameter->isDefaultValueAvailable()) {
157
                    if (\PHP_MAJOR_VERSION < 8) {
158
                        $resolvedParameters[$position] = Reflection::getParameterDefaultValue($parameter);
159
                    } else {
160
                        ++$nullValuesFound;
161
                    }
162
                } elseif (!$parameter->isVariadic()) {
163
                    $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...
164
                }
165
166
                continue;
167
            }
168
169
            if ($parameter->isVariadic() && \is_array($resolved)) {
170
                $resolvedParameters = \array_merge($resolvedParameters, $resolved);
171
                continue;
172
            }
173
174
            $resolvedParameters[$position] = $resolved;
175
        }
176
177
        return $resolvedParameters;
178
    }
179
180
    /**
181
     * Resolve a service definition, class string, invocable object or callable
182
     * using autowiring.
183
     *
184
     * @param string|callable|object  $callback
185
     * @param array<int|string,mixed> $args
186
     *
187
     * @throws ContainerResolutionException|\ReflectionException if unresolvable
188
     *
189
     * @return mixed
190
     */
191
    public function resolve($callback, array $args = [])
192
    {
193
        if ($callback instanceof Definitions\Parameter) {
194
            if (!\array_key_exists($param = (string) $callback, $this->container->parameters)) {
195
                if (!$callback->shouldResolve()) {
196
                    throw new ContainerResolutionException(\sprintf('The parameter "%s" is not defined.', $param));
197
                }
198
199
                return null === $this->builder ? $this->container->parameter($param) : $this->builder->methodCall(new Expr\Variable('this'), 'parameter', [$param]);
200
            }
201
202
            if (null !== $this->builder) {
203
                return $resolved = new Expr\ArrayDimFetch($this->builder->propertyFetch(new Expr\Variable('this'), 'parameters'), new String_($param));
0 ignored issues
show
Unused Code introduced by
The assignment to $resolved is dead and can be removed.
Loading history...
204
            }
205
206
            $resolved = $this->container->parameters[$param];
207
        } elseif ($callback instanceof Definitions\Statement) {
208
            $resolved = $this->resolve($callback->getValue(), $callback->getArguments() + $args);
209
210
            if ($callback->isClosureWrappable()) {
211
                $resolved = null === $this->builder ? fn () => $resolved : new Expr\ArrowFunction(['expr' => $resolved]);
212
            }
213
        } elseif ($callback instanceof Definitions\Reference) {
214
            $resolved = $this->resolveReference((string) $callback);
215
216
            if (\is_callable($resolved) || (\is_array($resolved) && 2 === \count($resolved, \COUNT_RECURSIVE))) {
217
                $resolved = $this->resolveCallable($resolved, $args);
218
            } else {
219
                $callback = $resolved;
220
            }
221
        } elseif ($callback instanceof Definitions\ValueDefinition) {
222
            $resolved = $callback->getEntity();
223
        } elseif ($callback instanceof Definitions\TaggedLocator) {
224
            $resolved = $this->resolve($callback->resolve($this->container));
225
        } elseif ($callback instanceof Builder\PhpLiteral) {
226
            $expression = $this->literalCache[\spl_object_id($callback)] ??= $callback->resolve($this)[0];
227
            $resolved = $expression instanceof Stmt\Expression ? $expression->expr : $expression;
228
        } elseif (Services\ServiceLocator::class === $callback) {
229
            $services = [];
230
231
            foreach ($args as $name => $service) {
232
                $services += $this->resolveServiceSubscriber($name, (string) $service);
233
            }
234
            $resolved = null === $this->builder ? new Services\ServiceLocator($services) : $this->builder->new('\\' . Services\ServiceLocator::class, [$services]);
235
        } elseif (\is_string($callback)) {
236
            if (\str_contains($callback, '%')) {
237
                $callback = $this->container->parameter($callback);
238
            }
239
240
            if (\class_exists($callback)) {
241
                return $this->resolveClass($callback, $args);
242
            }
243
244
            if (\is_callable($callback)) {
245
                $resolved = $this->resolveCallable($callback, $args);
246
            }
247
        } elseif (\is_callable($callback) || \is_array($callback)) {
248
            $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

248
            $resolved = $this->resolveCallable(/** @scrutinizer ignore-type */ $callback, $args);
Loading history...
249
        }
250
251
        return $resolved ?? (null === $this->builder ? $callback : $this->builder->val($callback));
252
    }
253
254
    /**
255
     * Resolves callables and array like callables.
256
     *
257
     * @param callable|array<int,mixed> $callback
258
     * @param array<int|string,mixed>   $arguments
259
     *
260
     * @throws \ReflectionException if $callback is not a real callable
261
     *
262
     * @return mixed
263
     */
264
    public function resolveCallable($callback, array $arguments = [])
265
    {
266
        if (\is_array($callback)) {
267
            if ((2 === \count($callback) && \array_is_list($callback)) && \is_string($callback[1])) {
268
                $callback[0] = $this->resolve($callback[0]);
269
270
                if ($callback[0] instanceof Expr\BinaryOp\Coalesce) {
271
                    $class = self::getDefinitionClass($this->container->definition($callback[0]->left->dim->value));
272
273
                    if (null !== $class) {
274
                        $type = [$class, $callback[1]];
275
                    }
276
                } elseif ($callback[0] instanceof Expr\New_) {
277
                    $type = [(string) $callback[0]->class, $callback[1]];
278
                }
279
280
                if (isset($type) || \is_callable($callback)) {
281
                    goto create_callable;
282
                }
283
            }
284
285
            $callback = $this->resolveArguments($callback);
286
287
            return null === $this->builder ? $callback : $this->builder->val($callback);
288
        }
289
290
        create_callable:
291
        $args = $this->autowireArguments($ref = Callback::toReflection($type ?? $callback), $arguments);
292
293
        if ($ref instanceof \ReflectionFunction) {
294
            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

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

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