Completed
Push — master ( a1d4cc...333bc3 )
by Neomerx
05:54
created

Router::replacePlaceholders()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 40
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 40
c 0
b 0
f 0
ccs 28
cts 28
cp 1
rs 5.3846
cc 8
eloc 32
nc 8
nop 2
crap 8
1
<?php namespace Limoncello\Core\Routing;
2
3
/**
4
 * Copyright 2015-2016 [email protected] (www.neomerx.com)
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\RouteCollector;
20
use FastRoute\RouteParser\Std;
21
use Limoncello\Core\Contracts\Routing\DispatcherInterface;
22
use Limoncello\Core\Contracts\Routing\GroupInterface;
23
use Limoncello\Core\Contracts\Routing\RouteInterface;
24
use Limoncello\Core\Contracts\Routing\RouterInterface;
25
use LogicException;
26
use Psr\Http\Message\ServerRequestInterface;
27
28
/**
29
 * @package Limoncello\Core
30
 */
31
class Router implements RouterInterface
32
{
33
    /**
34
     * @var false|array
35
     */
36
    private $cachedRoutes = false;
37
38
    /**
39
     * @var string
40
     */
41
    private $generatorClass;
42
43
    /**
44
     * @var string
45
     */
46
    private $dispatcherClass;
47
48
    /**
49
     * @var DispatcherInterface
50
     */
51
    private $dispatcher;
52
53
    /**
54
     * @param string $generatorClass
55
     * @param string $dispatcherClass
56
     */
57 12
    public function __construct($generatorClass, $dispatcherClass)
58
    {
59 12
        $this->generatorClass  = $generatorClass;
60 12
        $this->dispatcherClass = $dispatcherClass;
61 12
    }
62
63
    /**
64
     * @inheritdoc
65
     */
66 11
    public function getCachedRoutes(GroupInterface $group)
67
    {
68 11
        $collector = $this->createRouteCollector();
69
70 11
        $routeIndex         = 0;
71 11
        $allRoutesInfo      = [];
72 11
        $namedRouteUriPaths = [];
73 11
        foreach ($group->getRoutes() as $route) {
74
            /** @var RouteInterface $route */
75 11
            $allRoutesInfo[] = [
76 11
                $route->getHandler(),
77 11
                $route->getMiddleware(),
78 11
                $route->getContainerConfigurators(),
79 11
                $route->getRequestFactory(),
80
            ];
81
82 11
            $routeName = $route->getName();
83 11
            if (empty($routeName) === false) {
84 6
                $namedRouteUriPaths[$routeName] = $route->getUriPath();
85
            }
86
87 11
            $collector->addRoute($route->getMethod(), $route->getUriPath(), $routeIndex);
88
89 11
            $routeIndex++;
90
        }
91
92 11
        return [$collector->getData(), $allRoutesInfo, $namedRouteUriPaths];
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98 11
    public function loadCachedRoutes(array $cachedRoutes)
99
    {
100 11
        $this->cachedRoutes  = $cachedRoutes;
101 11
        list($collectorData) = $cachedRoutes;
102
103 11
        $this->dispatcher = $this->createDispatcher();
104 11
        $this->dispatcher->setData($collectorData);
105 11
    }
106
107
    /**
108
     * @inheritdoc
109
     */
110 11
    public function match($method, $uriPath)
111
    {
112 11
        $this->checkRoutesLoaded();
113
114 10
        $result = $this->dispatcher->dispatch($method, $uriPath);
115
116
        // Array contains matching result code, allowed methods list, handler parameters list, handler,
117
        // middleware list, container configurators list, custom request factory.
118 10
        switch ($result[0]) {
119 10
            case DispatcherInterface::FOUND:
120 8
                $routeIndex    = $result[1];
121 8
                $handlerParams = $result[2];
122
123 8
                list(, $allRoutesInfo) = $this->cachedRoutes;
124 8
                $routeInfo             = $allRoutesInfo[$routeIndex];
125
126 8
                return array_merge([self::MATCH_FOUND, null, $handlerParams], $routeInfo);
127
128 6
            case DispatcherInterface::METHOD_NOT_ALLOWED:
129 5
                $allowedMethods = $result[1];
130
131 5
                return [self::MATCH_METHOD_NOT_ALLOWED, $allowedMethods, null, null, null, null, null];
132
133
            default:
134 5
                return [self::MATCH_NOT_FOUND, null, null, null, null, null, null];
135
        }
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141 5
    public function getUriPath($routeName)
142
    {
143 5
        $this->checkRoutesLoaded();
144
145 5
        list(, , $namedRouteUriPaths) = $this->cachedRoutes;
146
147 5
        $result = array_key_exists($routeName, $namedRouteUriPaths) === true ? $namedRouteUriPaths[$routeName] : null;
148
149 5
        return $result;
150
    }
151
152
    /**
153
     * @inheritdoc
154
     */
155 1
    public function get(
156
        ServerRequestInterface $request,
157
        $routeName,
158
        array $placeholders = [],
159
        array $queryParams = []
160
    ) {
161 1
        $prefix = $this->getServerUriPrefix($request);
162 1
        $path   = $this->getUriPath($routeName);
163 1
        $path   = $this->replacePlaceholders($path, $placeholders);
164 1
        $url    = empty($queryParams) === true ? "$prefix$path" : "$prefix$path?" . http_build_query($queryParams);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $prefix instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $path instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
165
166 1
        return $url;
167
    }
168
169
    /**
170
     * @return RouteCollector
171
     */
172 11
    protected function createRouteCollector()
173
    {
174 11
        return new RouteCollector(new Std(), new $this->generatorClass);
175
    }
176
177
    /**
178
     * @return DispatcherInterface
179
     */
180 11
    protected function createDispatcher()
181
    {
182 11
        return new $this->dispatcherClass;
183
    }
184
185
    /**
186
     * @param ServerRequestInterface $request
187
     *
188
     * @return string
189
     */
190 1
    private function getServerUriPrefix(ServerRequestInterface $request)
191
    {
192 1
        $uri       = $request->getUri();
193 1
        $uriScheme = $uri->getScheme();
194 1
        $uriHost   = $uri->getHost();
195 1
        $uriPort   = $uri->getPort();
196 1
        $prefix    = empty($uriPort) === true ? "$uriScheme://$uriHost" : "$uriScheme://$uriHost:$uriPort";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $uriScheme instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $uriHost instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $uriPort instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
197
198 1
        return $prefix;
199
    }
200
201
    /**
202
     * @param string $path
203
     * @param array  $placeholders
204
     *
205
     * @return string
206
     */
207 1
    private function replacePlaceholders($path, array $placeholders)
208
    {
209 1
        $result            = '';
210 1
        $inPlaceholder     = false;
211 1
        $curPlaceholder    = null;
212 1
        $inPlaceholderName = false;
213 1
        $pathLength        = strlen($path);
214 1
        for ($index = 0; $index < $pathLength; ++$index) {
215 1
            $character = $path[$index];
216
            switch ($character) {
217 1
                case '{':
218 1
                    $inPlaceholder     = true;
219 1
                    $inPlaceholderName = true;
220 1
                    break;
221 1
                case '}':
222 1
                    if (array_key_exists($curPlaceholder, $placeholders) === true) {
223 1
                        $result .= $placeholders[$curPlaceholder];
224
                    } else {
225 1
                        $result .= '{' . $curPlaceholder . '}';
226
                    }
227 1
                    $inPlaceholder     = false;
228 1
                    $curPlaceholder    = null;
229 1
                    $inPlaceholderName = false;
230 1
                    break;
231
                default:
232 1
                    if ($inPlaceholder === false) {
233 1
                        $result .= $character;
234
                    } else {
235 1
                        if ($character === ':') {
236 1
                            $inPlaceholderName = false;
237 1
                        } elseif ($inPlaceholderName === true) {
238 1
                            $curPlaceholder .= $character;
239
                        }
240
                    }
241 1
                    break;
242
            }
243
        }
244
245 1
        return $result;
246
    }
247
248
    /**
249
     * @return void
250
     */
251 12
    private function checkRoutesLoaded()
252
    {
253 12
        if ($this->cachedRoutes === false) {
254 1
            throw new LogicException('Routes are not loaded yet.');
255
        }
256 11
    }
257
}
258