Test Failed
Branch master (effa58)
by Divine Niiquaye
02:13
created

RouteInvoker::__invoke()   C

Complexity

Conditions 16
Paths 56

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 27
nc 56
nop 2
dl 0
loc 44
rs 5.5666
c 2
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 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
    public function __construct(ContainerInterface $container = null)
36
    {
37
        $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
    public function __invoke($handler, array $arguments)
49
    {
50
        if (\is_string($handler)) {
51
            if (null !== $this->container && $this->container->has($handler)) {
52
                $handler = $this->container->get($handler);
53
            } elseif (\str_contains($handler, '@')) {
54
                $handler = \explode('@', $handler, 2);
55
56
                goto maybe_callable;
57
            } elseif (\class_exists($handler)) {
58
                $handlerRef = new \ReflectionClass($handler);
59
60
                if ($handlerRef->hasMethod('__invoke')) {
61
                    $handler = [$handler, '__invoke'];
62
63
                    goto maybe_callable;
64
                }
65
66
                if (null === $constructor = $handlerRef->getConstructor()) {
67
                    return $handlerRef->newInstanceWithoutConstructor();
68
                }
69
70
                return $handlerRef->newInstanceArgs($this->resolveParameters($constructor->getParameters(), $arguments));
71
            }
72
        }
73
74
        if ((\is_array($handler) && [0, 1] === \array_keys($handler)) && \is_string($handler[0])) {
75
            maybe_callable:
76
            if (null !== $this->container && $this->container->has($handler[0])) {
77
                $handler[0] = $this->container->get($handler[0]);
78
            } elseif (\class_exists($handler[0])) {
79
                $handler[0] = (new \ReflectionClass($handler[0]))->newInstanceArgs([]);
80
            }
81
        }
82
83
        if (\is_callable($handler)) {
84
            $handlerRef = new \ReflectionFunction(\Closure::fromCallable($handler));
85
        } elseif (\is_object($handler)) {
86
            return $handler;
87
        } else {
88
            throw new InvalidControllerException(\sprintf('Route has an invalid handler type of "%s".', \gettype($handler)));
89
        }
90
91
        return $handlerRef->invokeArgs($this->resolveParameters($handlerRef->getParameters(), $arguments));
92
    }
93
94
    /**
95
     * @param array<int,\ReflectionParameter> $refParameters
96
     * @param array<string,mixed>             $arguments
97
     *
98
     * @return array<int,mixed>
99
     */
100
    private function resolveParameters(array $refParameters, array $arguments): array
101
    {
102
        $parameters = [];
103
104
        foreach ($refParameters as $index => $parameter) {
105
            $typeHint = $parameter->getType();
106
107
            if ($typeHint instanceof \ReflectionUnionType) {
108
                foreach ($typeHint->getTypes() as $unionType) {
109
                    if (isset($arguments[$unionType->getName()])) {
110
                        $parameters[$index] = $arguments[$unionType->getName()];
111
112
                        continue 2;
113
                    }
114
115
                    if (null !== $this->container && $this->container->has($unionType->getName())) {
116
                        $parameters[$index] = $this->container->get($unionType->getName());
117
118
                        continue 2;
119
                    }
120
                }
121
            } elseif ($typeHint instanceof \ReflectionNamedType) {
122
                if (isset($arguments[$typeHint->getName()])) {
123
                    $parameters[$index] = $arguments[$typeHint->getName()];
124
125
                    continue;
126
                }
127
128
                if (null !== $this->container && $this->container->has($typeHint->getName())) {
129
                    $parameters[$index] = $this->container->get($typeHint->getName());
130
131
                    continue;
132
                }
133
            }
134
135
            if (isset($arguments[$parameter->getName()])) {
136
                $parameters[$index] = $arguments[$parameter->getName()];
137
            } elseif (null !== $this->container && $this->container->has($parameter->getName())) {
138
                $parameters[$index] = $this->container->get($parameter->getName());
139
            } elseif ($parameter->allowsNull() && !$parameter->isDefaultValueAvailable()) {
140
                $parameters[$index] = null;
141
            }
142
        }
143
144
        return $parameters;
145
    }
146
}
147