Test Failed
Pull Request — master (#16)
by Divine Niiquaye
10:53
created

RouteMatcher::match()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 1 Features 0
Metric Value
cc 7
eloc 14
c 10
b 1
f 0
nc 5
nop 2
dl 0
loc 25
rs 8.8333
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;
19
20
use Flight\Routing\Routes\{FastRoute as Route, Route as BaseRoute};
21
use Flight\Routing\Exceptions\{UriHandlerException, UrlGenerationException};
22
use Flight\Routing\Interfaces\{RouteCompilerInterface, RouteMapInterface, RouteMatcherInterface};
23
use Psr\Http\Message\{ServerRequestInterface, UriInterface};
24
25
/**
26
 * The bidirectional route matcher responsible for matching
27
 * HTTP request and generating url from routes.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
class RouteMatcher implements RouteMatcherInterface, \Countable
32
{
33
    /** @var array<int,Route> */
34
    protected $routes;
35
36
    /** @var array<string,mixed> */
37
    protected $staticRouteMap;
38
39
    /** @var array<int,mixed> */
40
    protected $dynamicRouteMap;
41
42
    /** @var RouteCompilerInterface */
43
    private $compiler;
44
45
    public function __construct(RouteMapInterface $collection)
46
    {
47
        $this->compiler = $collection->getCompiler();
48
49
        $this->routes = $collection['routes'] ?? [];
50
        $this->staticRouteMap = $collection['staticRoutesMap'] ?? [];
51
        $this->dynamicRouteMap = $collection['dynamicRoutesMap'] ?? [];
52
    }
53
54
    /**
55
     * Get the total number of routes.
56
     */
57
    public function count(): int
58
    {
59
        return \count($this->routes);
60
    }
61
62
    /**
63
     * Get routes associated with this matcher.
64
     *
65
     * @return Route[]
66
     */
67
    public function getRoutes(): array
68
    {
69
        return $this->routes;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function matchRequest(ServerRequestInterface $request): ?Route
76
    {
77
        $requestUri = $request->getUri();
78
79
        // Resolve request path to match sub-directory or /index.php/path
80
        if (!empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
81
            $requestUri = $requestUri->withPath($pathInfo);
82
        }
83
84
        return $this->match($request->getMethod(), $requestUri);
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public function match(string $method, UriInterface $uri): ?Route
91
    {
92
        $requestPath = \rtrim($pathInfo = $uri->getPath(), Route::URL_PREFIX_SLASHES[$pathInfo[-1]] ?? '/') ?: '/';
0 ignored issues
show
Bug introduced by
The constant Flight\Routing\Routes\Fa...ute::URL_PREFIX_SLASHES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
93
94
        if (isset($this->staticRouteMap[$requestPath])) {
95
            $staticRoute = $this->staticRouteMap[$requestPath];
96
            $route = $this->routes[$staticRoute[0]]->match($method, $uri);
97
98
            if (null === $hostsRegex = $staticRoute[1]) {
99
                return $route;
100
            }
101
102
            if (null === $variables = $this->matchStaticRouteHost($uri, $hostsRegex, $staticRoute[2])) {
103
                if (!empty($this->dynamicRouteMap)) {
104
                    goto retry_routing;
105
                }
106
107
                throw new UriHandlerException(\sprintf('Unfortunately current host "%s" is not allowed on requested static path [%s].', $uri->getHost(), $uri->getPath()), 400);
108
            }
109
110
            return $route->arguments($variables);
111
        }
112
113
        retry_routing:
114
        return $this->matchVariableRoute($method, $pathInfo === $requestPath ? $uri : $uri->withPath($requestPath));
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
121
    {
122
        foreach ($this->routes as $route) {
123
            if ($routeName === $route->get('name')) {
124
                return $this->compiler->generateUri($route, $parameters, \array_diff_key($route->get('defaults'), ['_arguments' => []]));
0 ignored issues
show
Bug introduced by
It seems like $route->get('defaults') can also be of type null; however, parameter $array1 of array_diff_key() does only seem to accept array, 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

124
                return $this->compiler->generateUri($route, $parameters, \array_diff_key(/** @scrutinizer ignore-type */ $route->get('defaults'), ['_arguments' => []]));
Loading history...
125
            }
126
        }
127
128
        throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404);
129
    }
130
131
    /**
132
     * Get the compiler associated with this class.
133
     */
134
    public function getCompiler(): RouteCompilerInterface
135
    {
136
        return $this->compiler;
137
    }
138
139
    protected function matchVariableRoute(string $method, UriInterface $uri): ?Route
140
    {
141
        $requestPath = \strpbrk((string) $uri, '/');
142
143
        foreach ($this->dynamicRouteMap[0] ?? [] as $regex) {
144
            if (!\preg_match($regex, $requestPath, $matches)) {
145
                continue;
146
            }
147
148
            $route = $this->routes[$routeId = (int) $matches['MARK']];
149
            $matchVar = 0;
150
151
            foreach ($this->dynamicRouteMap[1][$routeId] ?? [] as $key => $value) {
152
                $route->argument($key, $matches[++$matchVar] ?? $value);
153
            }
154
155
            return $route->match($method, $uri);
156
        }
157
158
        return null;
159
    }
160
161
    /**
162
     * @param array<string,string|null> $variables
163
     *
164
     * @return array<string,string|null>|null
165
     */
166
    protected function matchStaticRouteHost(UriInterface $uri, string $hostsRegex, array $variables): ?array
167
    {
168
        $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
169
170
        if (!\preg_match($hostsRegex, $hostAndPost, $hostsVar)) {
171
            return null;
172
        }
173
174
        foreach ($variables as $key => $var) {
175
            if (isset($hostsVar[$key])) {
176
                $variables[$key] = $hostsVar[$key] ?? $var;
177
            }
178
        }
179
180
        return $variables;
181
    }
182
}
183