Test Failed
Pull Request — master (#16)
by Divine Niiquaye
02:34
created

RouteInvoker::resolveParameters()   C

Complexity

Conditions 16
Paths 17

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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