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

RouteMatcher::match()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
cc 8
eloc 16
c 8
b 1
f 0
nc 9
nop 2
dl 0
loc 29
rs 8.4444
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 RouteCompilerInterface */
42
    private $compiler;
43
44
    public function __construct(RouteMapInterface $collection)
45
    {
46
        $this->compiler = $collection->getCompiler();
47
48
        $this->routes = $collection['routes'] ?? [];
49
        $this->staticRouteMap = $collection['staticRoutesMap'] ?? [];
50
        $this->dynamicRouteMap = $collection['dynamicRoutesMap'] ?? [];
51
    }
52
53
    /**
54
     * Get the total number of routes.
55
     */
56
    public function count(): int
57
    {
58
        return \count($this->routes);
59
    }
60
61
    /**
62
     * Get routes associated with this matcher.
63
     *
64
     * @return Route[]
65
     */
66
    public function getRoutes(): array
67
    {
68
        return $this->routes;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function matchRequest(ServerRequestInterface $request): ?Route
75
    {
76
        $requestUri = $request->getUri();
77
78
        // Resolve request path to match sub-directory or /index.php/path
79
        if (!empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
80
            $requestUri = $requestUri->withPath($pathInfo);
81
        }
82
83
        return $this->match($request->getMethod(), $requestUri);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function match(string $method, UriInterface $uri): ?Route
90
    {
91
        $requestPath = \rtrim($pathInfo = $uri->getPath(), Route::URL_PREFIX_SLASHES[$pathInfo[-1]] ?? '/') ?: '/';
92
93
        if (isset($this->staticRouteMap[$requestPath])) {
94
            [$routeId, $hostsRegex, $variables] = $this->staticRouteMap[$requestPath];
95
            $route = $this->routes[$routeId]->match($method, $uri);
96
97
            if (null !== $hostsRegex) {
98
                $variables = $this->matchStaticRouteHost($uri, $hostsRegex, $variables);
99
100
                if (null === $variables) {
101
                    if (!empty($this->dynamicRouteMap)) {
102
                        goto retry_routing;
103
                    }
104
105
                    throw new UriHandlerException(\sprintf('Unfortunately current host "%s" is not allowed on requested static path [%s].', $uri->getHost(), $uri->getPath()), 400);
106
                }
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
    /**
138
     * Get the compiler associated with this class.
139
     */
140
    public function getCompiler(): RouteCompilerInterface
141
    {
142
        return $this->compiler;
143
    }
144
145
    protected function matchVariableRoute(string $method, UriInterface $uri): ?Route
146
    {
147
        $requestPath = \strpbrk((string) $uri, '/');
148
149
        foreach ($this->dynamicRouteMap[0] ?? [] as $regex) {
150
            if (!\preg_match($regex, $requestPath, $matches)) {
151
                continue;
152
            }
153
154
            $route = $this->routes[$routeId = (int) $matches['MARK']];
155
            $matchVar = 0;
156
157
            foreach ($this->dynamicRouteMap[1][$routeId] ?? [] as $key => $value) {
158
                $route->argument($key, $matches[++$matchVar] ?? $value);
159
            }
160
161
            return $route->match($method, $uri);
162
        }
163
164
        return null;
165
    }
166
167
    /**
168
     * @param array<string,string|null> $variables
169
     *
170
     * @return array<string,string|null>|null
171
     */
172
    protected function matchStaticRouteHost(UriInterface $uri, string $hostsRegex, array $variables): ?array
173
    {
174
        $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
175
176
        if (!\preg_match($hostsRegex, $hostAndPost, $hostsVar)) {
177
            return null;
178
        }
179
180
        foreach ($variables as $key => $var) {
181
            if (isset($hostsVar[$key])) {
182
                $variables[$key] = $hostsVar[$key] ?? $var;
183
            }
184
        }
185
186
        return $variables;
187
    }
188
}
189