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

RouteInvoker::__construct()   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
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
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