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

RouteMatcher::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 2
eloc 4
c 4
b 1
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
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\Exceptions\{UriHandlerException, UrlGenerationException};
21
use Flight\Routing\Interfaces\{RouteCompilerInterface, RouteMapInterface, RouteMatcherInterface};
22
use Psr\Http\Message\{ServerRequestInterface, UriInterface};
23
24
/**
25
 * The bidirectional route matcher responsible for matching
26
 * HTTP request and generating url from routes.
27
 *
28
 * @author Divine Niiquaye Ibok <[email protected]>
29
 */
30
class RouteMatcher implements RouteMatcherInterface, \Countable
31
{
32
    /** @var array<int,Route> */
33
    protected $routes = [];
34
35
    /** @var array */
36
    protected $staticRouteMap = [];
37
38
    /** @var array */
39
    protected $dynamicRouteMap = [];
40
41
    /** @var DebugRoute|null */
42
    protected $debug;
43
44
    /** @var RouteCompilerInterface */
45
    private $compiler;
46
47
    public function __construct(RouteMapInterface $collection)
48
    {
49
        $this->compiler = $collection->getCompiler();
50
        [$this->routes, $this->staticRouteMap, $this->dynamicRouteMap] = $collection->getData();
51
52
        // Enable routes profiling ...
53
        if ($collection instanceof RouteCollection) {
54
            $this->debug = $collection->getDebugRoute();
55
        }
56
    }
57
58
    /**
59
     * Get the total number of routes.
60
     */
61
    public function count(): int
62
    {
63
        return \count($this->routes);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function matchRequest(ServerRequestInterface $request): ?Route
70
    {
71
        $requestUri = $request->getUri();
72
73
        // Resolve request path to match sub-directory or /index.php/path
74
        if (!empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
75
            $requestUri = $requestUri->withPath($pathInfo);
76
        }
77
78
        return $this->match($request->getMethod(), $requestUri);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function match(string $method, UriInterface $uri): ?Route
85
    {
86
        $pathInfo = $uri->getPath();
87
        $requestPath = \rtrim($pathInfo, Route::URL_PREFIX_SLASHES[$pathInfo[-1]] ?? '/') ?: '/';
88
89
        if (isset($this->staticRouteMap[$requestPath])) {
90
            [$routeId, $hostsRegex, $variables] = $this->staticRouteMap[$requestPath];
91
            $route = $this->routes[$routeId]->match($method, $uri);
92
93
            if (null !== $hostsRegex) {
94
                $variables = $this->matchStaticRouteHost($uri, $hostsRegex, $variables);
95
96
                if (null === $variables) {
97
                    if (!empty($this->dynamicRouteMap)) {
98
                        goto retry_routing;
99
                    }
100
101
                    throw new UriHandlerException(\sprintf('Unfortunately current host "%s" is not allowed on requested static path [%s].', $uri->getHost(), $uri->getPath()), 400);
102
                }
103
            }
104
105
            if (null !== $this->debug) {
106
                $this->debug->setMatched($route, $routeId);
107
            }
108
109
            return empty($variables) ? $route : $route->arguments($variables);
110
        }
111
112
        retry_routing:
113
        if ($pathInfo !== $requestPath) {
114
            $uri = $uri->withPath($requestPath);
115
        }
116
117
        return $this->matchVariableRoute($method, $uri);
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
124
    {
125
        foreach ($this->routes as $route) {
126
            if ($routeName === $route->get('name')) {
127
                $defaults = $route->get('defaults');
128
                unset($defaults['_arguments']);
129
130
                return $this->compiler->generateUri($route, $parameters, $defaults);
131
            }
132
        }
133
134
        throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404);
135
    }
136
137
    public function getCompiler(): RouteCompilerInterface
138
    {
139
        return $this->compiler;
140
    }
141
142
    /**
143
     * Get the profiled routes.
144
     */
145
    public function getProfile(): ?DebugRoute
146
    {
147
        return $this->debug;
148
    }
149
150
    protected function matchVariableRoute(string $method, UriInterface $uri): ?Route
151
    {
152
        $requestPath = \strpbrk((string) $uri, '/');
153
154
        foreach ($this->dynamicRouteMap[0] ?? [] as $regex) {
155
            if (!\preg_match($regex, $requestPath, $matches)) {
156
                continue;
157
            }
158
159
            $route = $this->routes[$routeId = (int) $matches['MARK']];
160
            $matchVar = 0;
161
162
            foreach ($this->dynamicRouteMap[1][$routeId] ?? [] as $key => $value) {
163
                $route->argument($key, $matches[++$matchVar] ?? $value);
164
            }
165
166
            if (null !== $this->debug) {
167
                $this->debug->setMatched($route, $routeId);
168
            }
169
170
            return $route->match($method, $uri);
171
        }
172
173
        return null;
174
    }
175
176
    /**
177
     * @param array<string,string|null> $variables
178
     *
179
     * @return array<string,string|null>|null
180
     */
181
    protected function matchStaticRouteHost(UriInterface $uri, string $hostsRegex, array $variables): ?array
182
    {
183
        $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
184
185
        if (!\preg_match($hostsRegex, $hostAndPost, $hostsVar)) {
186
            return null;
187
        }
188
189
        foreach ($variables as $key => $var) {
190
            if (isset($hostsVar[$key])) {
191
                $variables[$key] = $hostsVar[$key] ?? $var;
192
            }
193
        }
194
195
        return $variables;
196
    }
197
}
198