Passed
Push — master ( 545dab...a27896 )
by Divine Niiquaye
12:03
created

RouteInvoker::__invoke()   C

Complexity

Conditions 15
Paths 40

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 15.1689

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 15
eloc 22
c 3
b 0
f 0
nc 40
nop 2
dl 0
loc 35
ccs 20
cts 22
cp 0.9091
crap 15.1689
rs 5.9166

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 Flight Routing.
7
 *
8
 * PHP version 7.4 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 Flight\Routing\Handlers;
19
20
use Flight\Routing\Exceptions\InvalidControllerException;
21
use Psr\Container\ContainerInterface;
22
23
/**
24
 * Invokes a route's handler with arguments.
25
 *
26
 * If you're using this library with Rade-DI, Yii Inject, DivineNii PHP Invoker, or Laravel DI,
27
 * instead of using this class as callable, use the call method from the container's class.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
class RouteInvoker
32
{
33
    private ?ContainerInterface $container;
34
35 99
    public function __construct(ContainerInterface $container = null)
36
    {
37 99
        $this->container = $container;
38
    }
39
40
    /**
41
     * Auto-configure route handler parameters.
42
     *
43
     * @param mixed               $handler
44
     * @param array<string,mixed> $arguments
45
     *
46
     * @return mixed
47
     */
48 68
    public function __invoke($handler, array $arguments)
49
    {
50 68
        if (\is_string($handler)) {
51 12
            if (null !== $this->container && $this->container->has($handler)) {
52
                $handler = $this->container->get($handler);
53 12
            } elseif (\str_contains($handler, '@')) {
54 1
                $handler = \explode('@', $handler, 2);
55 1
                goto maybe_callable;
56 11
            } elseif (\class_exists($handler)) {
57 11
                $handler = new $handler();
58
            }
59 56
        } elseif ((\is_array($handler) && [0, 1] === \array_keys($handler)) && \is_string($handler[0])) {
60
            maybe_callable:
61 3
            if (null !== $this->container && $this->container->has($handler[0])) {
62
                $handler[0] = $this->container->get($handler[0]);
63 3
            } elseif (\class_exists($handler[0])) {
64 2
                $handler[0] = new $handler[0]();
65
            }
66
        }
67
68 68
        if (!\is_callable($handler)) {
69 13
            if (!\is_object($handler)) {
70 2
                throw new InvalidControllerException(\sprintf('Route has an invalid handler type of "%s".', \gettype($handler)));
71
            }
72
73 11
            return $handler;
74
        }
75
76 55
        $handlerRef = new \ReflectionFunction(\Closure::fromCallable($handler));
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type object; however, parameter $callback of Closure::fromCallable() does only seem to accept 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

76
        $handlerRef = new \ReflectionFunction(\Closure::fromCallable(/** @scrutinizer ignore-type */ $handler));
Loading history...
77
78 55
        if ($handlerRef->getNumberOfParameters() > 0) {
79 54
            $resolvedParameters = $this->resolveParameters($handlerRef->getParameters(), $arguments);
80
        }
81
82 55
        return $handlerRef->invokeArgs($resolvedParameters ?? []);
83
    }
84
85
    /**
86
     * @param array<int,\ReflectionParameter> $refParameters
87
     * @param array<string,mixed>             $arguments
88
     *
89
     * @return array<int,mixed>
90
     */
91 54
    private function resolveParameters(array $refParameters, array $arguments): array
92
    {
93 54
        $parameters = [];
94
95 54
        foreach ($refParameters as $index => $parameter) {
96 54
            $typeHint = $parameter->getType();
97
98 54
            if ($typeHint instanceof \ReflectionUnionType) {
99
                foreach ($typeHint->getTypes() as $unionType) {
100
                    if (isset($arguments[$unionType->getName()])) {
101
                        $parameters[$index] = $arguments[$unionType->getName()];
102
103
                        continue 2;
104
                    }
105
106
                    if (null !== $this->container && $this->container->has($unionType->getName())) {
107
                        $parameters[$index] = $this->container->get($unionType->getName());
108
109
                        continue 2;
110
                    }
111
                }
112 54
            } elseif ($typeHint instanceof \ReflectionNamedType) {
113 52
                if (isset($arguments[$typeHint->getName()])) {
114 43
                    $parameters[$index] = $arguments[$typeHint->getName()];
115
116 43
                    continue;
117
                }
118
119 9
                if (null !== $this->container && $this->container->has($typeHint->getName())) {
120
                    $parameters[$index] = $this->container->get($typeHint->getName());
121
122
                    continue;
123
                }
124
            }
125
126 11
            if (isset($arguments[$parameter->getName()])) {
127 5
                $parameters[$index] = $arguments[$parameter->getName()];
128 6
            } elseif (null !== $this->container && $this->container->has($parameter->getName())) {
129
                $parameters[$index] = $this->container->get($parameter->getName());
130 6
            } elseif ($parameter->allowsNull() && !$parameter->isDefaultValueAvailable()) {
131 1
                $parameters[$index] = null;
132
            }
133
        }
134
135 54
        return $parameters;
136
    }
137
}
138