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

RouteMatcher::__serialize()   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
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Generator\GeneratedUri;
23
use Flight\Routing\Interfaces\{RouteCompilerInterface, RouteMatcherInterface};
24
use Psr\Http\Message\{ServerRequestInterface, UriInterface};
25
26
/**
27
 * The bidirectional route matcher responsible for matching
28
 * HTTP request and generating url from routes.
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
final class RouteMatcher implements RouteMatcherInterface
33
{
34
    /** @var array<int,Route>|array<int,mixed> */
35
    private $routes;
36
37
    /** @var RouteCompilerInterface */
38
    private $compiler;
39
40
    /** @var string|null */
41
    private $generatedRegex;
42
43
    /**
44
     * @var callable
45
     *
46
     * @internal Returns an optimised routes data.
47
     */
48
    private $beforeSerialization = [Generator\RegexGenerator::class, 'beforeCaching'];
49
50
    /**
51
     * @param RouteCompilerInterface|null $compiler
52
     */
53
    public function __construct(RouteCollection $collection, RouteCompilerInterface $compiler = null)
54
    {
55
        $this->compiler = $compiler ?? new RouteCompiler();
56
        $this->routes = $collection->getRoutes();
57
    }
58
59
    /**
60
     * @internal
61
     */
62
    public function __serialize(): array
63
    {
64
        return ($this->beforeSerialization)($this->compiler, $this->getRoutes());
65
    }
66
67
    /**
68
     * @internal
69
     *
70
     * @param array<string,mixed>
71
     */
72
    public function __unserialize(array $data): void
73
    {
74
        [$this->generatedRegex, $this->routes, $this->compiler] = $data;
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function matchRequest(ServerRequestInterface $request): ?Route
81
    {
82
        $requestUri = $request->getUri();
83
84
        // Resolve request path to match sub-directory or /index.php/path
85
        if (!empty($pathInfo = $request->getServerParams()['PATH_INFO'] ?? '')) {
86
            $requestUri = $requestUri->withPath($pathInfo);
87
        }
88
89
        return $this->match($request->getMethod(), $requestUri);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function match(string $method, UriInterface $uri): ?Route
96
    {
97
        $requestPath = $uri->getPath();
98
99
        if (isset(BaseRoute::URL_PREFIX_SLASHES[$requestPath[-1]])) {
100
            $requestPath = \substr($requestPath, 0, -1) ?: '/';
101
        }
102
103
        if (null === $this->generatedRegex) {
104
            foreach ($this->routes as $route) {
105
                [$pathRegex, $hostsRegex, $variables] = $this->compiler->compile($route);
106
107
                if ($pathRegex === $requestPath || 1 === \preg_match('#^' . $pathRegex . '$#u', $requestPath, $matches)) {
108
                    if (empty($variables)) {
109
                        return $route->match($method, $uri);
110
                    }
111
112
                    return self::doMatch($route, $uri, $hostsRegex, [$method, $variables, $matches ?? []]);
113
                }
114
            }
115
        } elseif (1 === \preg_match($this->generatedRegex, $requestPath, $matches, \PREG_UNMATCHED_AS_NULL)) {
116
            [$route, $hostsRegex, $variables] = $this->routes[$matches['MARK']];
117
118
            return self::doMatch($route, $uri, $hostsRegex, [$method, $variables, \array_filter($matches)]);
119
        }
120
121
        return null;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
128
    {
129
        foreach ($this->routes as $route) {
130
            if (!$route instanceof Route) {
131
                $route = $route[0];
132
            }
133
134
            if ($routeName === $route->get('name')) {
135
                return $this->compiler->generateUri($route, $parameters);
136
            }
137
        }
138
139
        throw new UrlGenerationException(\sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $routeName), 404);
140
    }
141
142
    /**
143
     * Get the compiler associated with this class.
144
     */
145
    public function getCompiler(): RouteCompilerInterface
146
    {
147
        return $this->compiler;
148
    }
149
150
    /**
151
     * Get the routes associated with this class.
152
     *
153
     * @return Route[]
154
     */
155
    public function getRoutes(): array
156
    {
157
        $routes = $this->routes;
158
159
        if (null !== $this->generatedRegex) {
160
            \array_walk($routes, static function (&$data): void {
161
                $data = $data[0];
162
            });
163
        }
164
165
        return $routes;
166
    }
167
168
    /**
169
     * @param array<int,mixed> $routeData
170
     */
171
    private static function doMatch(Route $route, UriInterface $uri, ?string $hostsRegex, array $routeData): Route
172
    {
173
        [$method, $variables, $matches] = $routeData;
174
        $matchVar = 0;
175
176
        if (!empty($hostsRegex)) {
177
            $hostAndPost = $uri->getHost() . (null !== $uri->getPort() ? ':' . $uri->getPort() : '');
178
179
            if (1 !== \preg_match('#^' . $hostsRegex . '$#i', $hostAndPost, $hostsVar)) {
180
                throw new UriHandlerException(\sprintf('Unfortunately current host "%s" is not allowed on requested path [%s].', $uri->getHost(), $uri->getPath()), 400);
181
            }
182
        }
183
184
        foreach ($variables as $key => $value) {
185
            $route->argument($key, $matches[++$matchVar] ?? $matches[$key] ?? $hostsVar[$key] ?? $value);
186
        }
187
188
        return $route->match($method, $uri);
189
    }
190
}
191