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

RouteMatcher::matchVariableRoute()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 20
rs 9.9332
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<string,mixed> */
36
    protected $staticRouteMap;
37
38
    /** @var array<int,mixed> */
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 (!empty($staticRoute = $this->staticRouteMap[$requestPath] ?? null)) {
94
            $route = $this->routes[$staticRoute[0]]->match($method, $uri);
95
96
            if (null === $hostsRegex = $staticRoute[1]) {
97
                return $route;
98
            }
99
100
            if (null === $variables = $this->matchStaticRouteHost($uri, $hostsRegex, $staticRoute[2])) {
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
            return $route->arguments($variables);
109
        }
110
111
        retry_routing:
112
        return $this->matchVariableRoute($method, $pathInfo === $requestPath ? $uri : $uri->withPath($requestPath));
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
119
    {
120
        foreach ($this->routes as $route) {
121
            if ($routeName === $route->get('name')) {
122
                return $this->compiler->generateUri($route, $parameters, \array_diff_key($route->get('defaults'), ['_arguments' => []]));
123
            }
124
        }
125
126
        throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404);
127
    }
128
129
    /**
130
     * Get the compiler associated with this class.
131
     */
132
    public function getCompiler(): RouteCompilerInterface
133
    {
134
        return $this->compiler;
135
    }
136
137
    protected function matchVariableRoute(string $method, UriInterface $uri): ?Route
138
    {
139
        $requestPath = \strpbrk((string) $uri, '/');
140
141
        foreach ($this->dynamicRouteMap[0] ?? [] as $regex) {
142
            if (!\preg_match($regex, $requestPath, $matches)) {
143
                continue;
144
            }
145
146
            $route = $this->routes[$routeId = (int) $matches['MARK']];
147
            $matchVar = 0;
148
149
            foreach ($this->dynamicRouteMap[1][$routeId] ?? [] as $key => $value) {
150
                $route->argument($key, $matches[++$matchVar] ?? $value);
151
            }
152
153
            return $route->match($method, $uri);
154
        }
155
156
        return null;
157
    }
158
159
    /**
160
     * @param array<string,string|null> $variables
161
     *
162
     * @return array<string,string|null>|null
163
     */
164
    protected function matchStaticRouteHost(UriInterface $uri, string $hostsRegex, array $variables): ?array
165
    {
166
        $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
167
168
        if (!\preg_match($hostsRegex, $hostAndPost, $hostsVar)) {
169
            return null;
170
        }
171
172
        foreach ($variables as $key => $var) {
173
            if (isset($hostsVar[$key])) {
174
                $variables[$key] = $hostsVar[$key] ?? $var;
175
            }
176
        }
177
178
        return $variables;
179
    }
180
}
181