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

RouteMatcher::getProfile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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\{MethodNotAllowedException, UriHandlerException, UrlGenerationException};
21
use Flight\Routing\Interfaces\{RouteCompilerInterface, 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
31
{
32
    /** @var iterable<int,Route> */
33
    protected $routes = [];
34
35
    /** @var array<string,array<string,mixed>> */
36
    protected $staticRouteMap = [];
37
38
    /** @var array<int,array> */
39
    protected $variableRouteData = [];
40
41
    /** @var string|null */
42
    protected $regexToRoutesMap = null;
43
44
    /** @var RouteCompilerInterface */
45
    private $compiler;
46
47
    /** @var DebugRoute|null */
48
    private $debug;
49
50
    public function __construct(RouteCollection $collection)
51
    {
52
        $this->compiler = $collection->getCompiler();
53
        $this->routes = $collection->getIterator();
54
55
        // Load the route maps from $collection.
56
        [$this->staticRouteMap, $dynamicRouteMap, $this->variableRouteData] = $collection->getRouteMaps();
57
58
        if (!empty($dynamicRouteMap)) {
59
            $this->regexToRoutesMap = '~^(?|' . \implode('|', $dynamicRouteMap) . ')$~u';
60
        }
61
62
        // Enable routes profiling ...
63
        $this->debug = $collection->getDebugRoute();
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
        $requestPath = $uri->getPath();
87
88
        if ('/' !== $requestPath && isset(Route::URL_PREFIX_SLASHES[$requestPath[-1]])) {
89
            $uri = $uri->withPath($requestPath = \substr($requestPath, 0, -1));
90
        }
91
92
        if (isset($this->staticRouteMap[$requestPath])) {
93
            $staticRoute = $this->staticRouteMap[$requestPath];
94
95
            if (!isset($staticRoute[$method])) {
96
                throw new MethodNotAllowedException(\array_keys($staticRoute), $uri->getPath(), $method);
97
            }
98
99
            if (!empty($hostsRegex = $staticRoute[$method][0])) {
100
                $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
101
102
                if (!\preg_match($hostsRegex, $hostAndPost, $hostsVar)) {
103
                    if (isset($this->regexToRoutesMap)) {
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
111
            $route = $this->routes[$routeId = $staticRoute[$method][1]];
112
113
            return $this->matchRoute($route, $uri, \array_merge($this->variableRouteData[$routeId], $hostsVar ?? []));
114
        } 
115
116
        retry_routing:
117
        if (isset($this->regexToRoutesMap) && \preg_match($this->regexToRoutesMap, $method . \strpbrk((string) $uri, '/'), $matches)) {
0 ignored issues
show
Bug introduced by
It seems like $this->regexToRoutesMap can also be of type null; however, parameter $pattern of preg_match() does only seem to accept string, 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

117
        if (isset($this->regexToRoutesMap) && \preg_match(/** @scrutinizer ignore-type */ $this->regexToRoutesMap, $method . \strpbrk((string) $uri, '/'), $matches)) {
Loading history...
118
            $route = $this->routes[$matches['MARK']];
119
120
            if (!empty($matches[1])) {
121
                throw new MethodNotAllowedException($route->get('methods'), $uri->getPath(), $method);
122
            }
123
124
            return $this->matchRoute($route, $uri, \array_merge($this->variableRouteData[$matches['MARK']], $matches));
125
        }
126
127
        return null;
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
134
    {
135
        foreach ($this->routes as $route) {
136
            if ($routeName === $route->get('name')) {
137
                $defaults = $route->get('defaults');
138
                unset($defaults['_arguments']);
139
140
                return $this->compiler->generateUri($route, $parameters, $defaults);
141
            }
142
        }
143
144
        throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404);
145
    }
146
147
    public function getCompiler(): RouteCompilerInterface
148
    {
149
        return $this->compiler;
150
    }
151
152
    /**
153
     * Get the profiled routes.
154
     */
155
    public function getProfile(): ?DebugRoute
156
    {
157
        return $this->debug;
158
    }
159
160
    protected function matchRoute(Route $route, UriInterface $uri, array $variables): Route
161
    {
162
        $schemes = $route->get('schemes');
163
164
        if (!empty($schemes) && !\array_key_exists($uri->getScheme(), $schemes)) {
165
            throw new UriHandlerException(\sprintf('Unfortunately current scheme "%s" is not allowed on requested uri [%s].', $uri->getScheme(), $uri->getPath()), 400);
166
        }
167
168
        foreach ($variables as $key => $value) {
169
            if (\is_int($key) || 'MARK' === $key) {
170
                continue;
171
            }
172
173
            $route->argument($key, $value);
174
        }
175
176
        if (null !== $this->debug) {
177
            $this->debug->setMatched($route->get('name'));
178
        }
179
180
        return $route;
181
    }
182
}
183