Completed
Branch master (8ae5a6)
by Neomerx
01:35
created

Router::checkRoutesLoaded()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
crap 2
1
<?php namespace Limoncello\Core\Routing;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use FastRoute\DataGenerator;
20
use FastRoute\Dispatcher;
21
use FastRoute\RouteCollector;
22
use FastRoute\RouteParser\Std;
23
use Limoncello\Contracts\Routing\DispatcherInterface;
24
use Limoncello\Contracts\Routing\GroupInterface;
25
use Limoncello\Contracts\Routing\RouteInterface;
26
use Limoncello\Contracts\Routing\RouterInterface;
27
use Limoncello\Core\Reflection\ClassIsTrait;
28
use LogicException;
29
use Psr\Http\Message\ServerRequestInterface;
30
31
/**
32
 * @package Limoncello\Core
33
 */
34
class Router implements RouterInterface
35
{
36
    use ClassIsTrait;
37
38
    /**
39
     * @var false|array
40
     */
41
    private $cachedRoutes = false;
42
43
    /**
44
     * @var string
45
     */
46
    private $generatorClass;
47
48
    /**
49
     * @var string
50
     */
51
    private $dispatcherClass;
52
53
    /**
54
     * @var DispatcherInterface
55
     */
56
    private $dispatcher;
57
58
    /**
59
     * @param string $generatorClass
60
     * @param string $dispatcherClass
61
     */
62 14
    public function __construct(string $generatorClass, string $dispatcherClass)
63
    {
64 14
        assert(static::classImplements($generatorClass, DataGenerator::class));
65 14
        assert(static::classImplements($dispatcherClass, Dispatcher::class));
66
67 14
        $this->generatorClass  = $generatorClass;
68 14
        $this->dispatcherClass = $dispatcherClass;
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74 13
    public function getCachedRoutes(GroupInterface $group): array
75
    {
76 13
        $collector = $this->createRouteCollector();
77
78 13
        $routeIndex         = 0;
79 13
        $allRoutesInfo      = [];
80 13
        $namedRouteUriPaths = [];
81 13
        foreach ($group->getRoutes() as $route) {
82
            /** @var RouteInterface $route */
83 13
            $allRoutesInfo[] = [
84 13
                $route->getHandler(),
85 13
                $route->getMiddleware(),
86 13
                $route->getContainerConfigurators(),
87 13
                $route->getRequestFactory(),
88
            ];
89
90 13
            $routeName = $route->getName();
91 13
            if (empty($routeName) === false) {
92 7
                $namedRouteUriPaths[$routeName] = $route->getUriPath();
93
            }
94
95 13
            $collector->addRoute($route->getMethod(), $route->getUriPath(), $routeIndex);
96
97 13
            $routeIndex++;
98
        }
99
100 13
        return [$collector->getData(), $allRoutesInfo, $namedRouteUriPaths];
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106 13
    public function loadCachedRoutes(array $cachedRoutes): void
107
    {
108 13
        $this->cachedRoutes  = $cachedRoutes;
109 13
        list($collectorData) = $cachedRoutes;
110
111 13
        $this->dispatcher = $this->createDispatcher();
112 13
        $this->dispatcher->setData($collectorData);
113
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118 12
    public function match(string $method, string $uriPath): array
119
    {
120 12
        $this->checkRoutesLoaded();
121
122 11
        $result = $this->dispatcher->dispatchRequest($method, $uriPath);
123
124
        // Array contains matching result code, allowed methods list, handler parameters list, handler,
125
        // middleware list, container configurators list, custom request factory.
126 11
        switch ($result[0]) {
127 11
            case DispatcherInterface::ROUTE_FOUND:
128 9
                $routeIndex    = $result[1];
129 9
                $handlerParams = $result[2];
130
131 9
                list(, $allRoutesInfo) = $this->cachedRoutes;
132 9
                $routeInfo             = $allRoutesInfo[$routeIndex];
133
134 9
                return array_merge([self::MATCH_FOUND, null, $handlerParams], $routeInfo);
135
136 6
            case DispatcherInterface::ROUTE_METHOD_NOT_ALLOWED:
137 5
                $allowedMethods = $result[1];
138
139 5
                return [self::MATCH_METHOD_NOT_ALLOWED, $allowedMethods, null, null, null, null, null];
140
141
            default:
142 5
                return [self::MATCH_NOT_FOUND, null, null, null, null, null, null];
143
        }
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149 5
    public function getUriPath(string $routeName): ?string
150
    {
151 5
        $this->checkRoutesLoaded();
152
153 5
        list(, , $namedRouteUriPaths) = $this->cachedRoutes;
154
155 5
        $result = array_key_exists($routeName, $namedRouteUriPaths) === true ? $namedRouteUriPaths[$routeName] : null;
156
157 5
        return $result;
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163 1
    public function get(
164
        string $hostUri,
165
        string $routeName,
166
        array $placeholders = [],
167
        array $queryParams = []
168
    ): string {
169 1
        $path   = $this->getUriPath($routeName);
170 1
        $path   = $path === null ? $path : $this->replacePlaceholders($path, $placeholders);
171 1
        $url    = empty($queryParams) === true ? "$hostUri$path" : "$hostUri$path?" . http_build_query($queryParams);
172
173 1
        return $url;
174
    }
175
176
    /**
177
     * @inheritdoc
178
     */
179 1
    public function getHostUri(ServerRequestInterface $request): string
180
    {
181 1
        $uri       = $request->getUri();
182 1
        $uriScheme = $uri->getScheme();
183 1
        $uriHost   = $uri->getHost();
184 1
        $uriPort   = $uri->getPort();
185 1
        $hostUri   = empty($uriPort) === true ? "$uriScheme://$uriHost" : "$uriScheme://$uriHost:$uriPort";
186
187 1
        return $hostUri;
188
    }
189
190
    /**
191
     * @return RouteCollector
192
     */
193 13
    protected function createRouteCollector(): RouteCollector
194
    {
195 13
        return new RouteCollector(new Std(), new $this->generatorClass);
196
    }
197
198
    /**
199
     * @return DispatcherInterface
200
     */
201 13
    protected function createDispatcher(): DispatcherInterface
202
    {
203 13
        return new $this->dispatcherClass;
204
    }
205
206
    /**
207
     * @param string $path
208
     * @param array  $placeholders
209
     *
210
     * @return string
211
     *
212
     * @SuppressWarnings(PHPMD.ElseExpression)
213
     */
214 1
    private function replacePlaceholders(string $path, array $placeholders): string
215
    {
216 1
        $result            = '';
217 1
        $inPlaceholder     = false;
218 1
        $curPlaceholder    = null;
219 1
        $inPlaceholderName = false;
220 1
        $pathLength        = strlen($path);
221 1
        for ($index = 0; $index < $pathLength; ++$index) {
222 1
            $character = $path[$index];
223
            switch ($character) {
224 1
                case '{':
225 1
                    $inPlaceholder     = true;
226 1
                    $inPlaceholderName = true;
227 1
                    break;
228 1
                case '}':
229 1
                    $result .= array_key_exists($curPlaceholder, $placeholders) === true ?
230 1
                        $placeholders[$curPlaceholder] : '{' . $curPlaceholder . '}';
231 1
                    $inPlaceholder     = false;
232 1
                    $curPlaceholder    = null;
233 1
                    $inPlaceholderName = false;
234 1
                    break;
235
                default:
236 1
                    if ($inPlaceholder === false) {
237 1
                        $result .= $character;
238
                    } else {
239 1
                        if ($character === ':') {
240 1
                            $inPlaceholderName = false;
241 1
                        } elseif ($inPlaceholderName === true) {
242 1
                            $curPlaceholder .= $character;
243
                        }
244
                    }
245 1
                    break;
246
            }
247
        }
248
249 1
        return $result;
250
    }
251
252
    /**
253
     * @return void
254
     */
255 13
    private function checkRoutesLoaded(): void
256
    {
257 13
        if ($this->cachedRoutes === false) {
258 1
            throw new LogicException('Routes are not loaded yet.');
259
        }
260
    }
261
}
262