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

RouteInvoker::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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.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 */
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
                    $constructorParameters = $constructor->getParameters();
69
70
                    goto resolve_handler;
71
                }
72
73
                return $handlerRef->newInstanceWithoutConstructor();
74
            }
75
        }
76
77
        if ((\is_array($handler) && [0, 1] === \array_keys($handler)) && \is_string($handler[0])) {
78
            maybe_callable:
79
            if (null !== $this->container && $this->container->has($handler[0])) {
80
                $handler[0] = $this->container->get($handler[0]);
81
            } elseif (\class_exists($handler[0])) {
82
                $handler[0] = (new \ReflectionClass($handler[0]))->newInstanceArgs();
83
            }
84
        }
85
86
        if (\is_callable($handler)) {
87
            $handlerRef = new \ReflectionFunction(\Closure::fromCallable($handler));
88
        } elseif (\is_object($handler)) {
89
            return $handler;
90
        } else {
91
            throw new InvalidControllerException(\sprintf('Route has an invalid handler type of "%s".', \gettype($handler)));
92
        }
93
94
        resolve_handler:
95
        $parameters = $this->resolveParameters($constructorParameters ?? $handlerRef->getParameters(), $arguments);
0 ignored issues
show
Bug introduced by
The method getParameters() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
        $parameters = $this->resolveParameters($constructorParameters ?? $handlerRef->/** @scrutinizer ignore-call */ getParameters(), $arguments);

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...
96
97
        if ($handlerRef instanceof \ReflectionFunction) {
98
            return $handlerRef->invokeArgs($parameters);
99
        }
100
101
        return $handlerRef->newInstanceArgs($parameters);
102
    }
103
104
    /**
105
     * @param array<int,\ReflectionParameter> $refParameters
106
     * @param array<string,mixed> $arguments
107
     *
108
     * @return array<int,mixed>
109
     */
110
    private function resolveParameters(array $refParameters, array $arguments): array
111
    {
112
        $parameters = [];
113
114
        foreach ($refParameters as $index => $parameter) {
115
            $typeHint = $parameter->getType();
116
117
            if ($typeHint instanceof \ReflectionUnionType) {
118
                foreach ($typeHint->getTypes() as $unionType) {
119
                    if (isset($arguments[$unionType->getName()])) {
120
                        $parameters[$index] = $arguments[$unionType->getName()];
121
122
                        continue 2;
123
                    }
124
125
                    if (null !== $this->container && $this->container->has($unionType->getName())) {
126
                        $parameter[$index] = $this->container->get($unionType->getName());
127
128
                        continue 2;
129
                    }
130
                }
131
            } elseif ($typeHint instanceof \ReflectionNamedType) {
132
                if (isset($arguments[$typeHint->getName()])) {
133
                    $parameters[$index] = $arguments[$typeHint->getName()];
134
135
                    continue;
136
                }
137
138
                if (null !== $this->container && $this->container->has($typeHint->getName())) {
139
                    $parameter[$index] = $this->container->get($typeHint->getName());
140
141
                    continue;
142
                }
143
            }
144
145
            if (isset($arguments[$parameter->getName()])) {
146
                $parameters[$index] = $arguments[$parameter->getName()];
147
            } elseif (null !== $this->container && $this->container->has($parameter->getName())) {
148
                $parameters[$index] = $this->container->get($typeHint->getName());
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
                $parameters[$index] = $this->container->get($typeHint->/** @scrutinizer ignore-call */ getName());
Loading history...
149
            } elseif ($parameter->allowsNull() && !$parameter->isDefaultValueAvailable()) {
150
                $parameters[$index] = null;
151
            }
152
        }
153
154
        return $parameters;
155
    }
156
}
157