Completed
Branch 4.0 (70d344)
by Marc André
02:49
created

Router::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 2 Features 1
Metric Value
c 9
b 2
f 1
dl 0
loc 21
rs 9.3142
cc 3
eloc 10
nc 3
nop 0
1
<?php
2
3
4
/**
5
 *
6
 * Copyright (c) 2010-2016 Nevraxe inc. & Marc André Audet <[email protected]>. All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without modification, are
9
 * permitted provided that the following conditions are met:
10
 *
11
 *   1. Redistributions of source code must retain the above copyright notice, this list of
12
 *       conditions and the following disclaimer.
13
 *
14
 *   2. Redistributions in binary form must reproduce the above copyright notice, this list
15
 *       of conditions and the following disclaimer in the documentation and/or other materials
16
 *       provided with the distribution.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL MARC ANDRÉ "MANHIM" AUDET BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 */
30
31
32
namespace Cervo\Libraries;
33
34
35
use Cervo\Core as _;
36
use Cervo\Libraries\Exceptions\InvalidMiddlewareException;
37
use Cervo\Libraries\Exceptions\InvalidRouterCacheException;
38
use Cervo\Libraries\Exceptions\RouteNotFoundException;
39
use FastRoute\RouteCollector;
40
use FastRoute\RouteParser as RouteParser;
41
use FastRoute\DataGenerator as DataGenerator;
42
use FastRoute\Dispatcher as Dispatcher;
43
44
45
/**
46
 * Route manager for Cervo.
47
 *
48
 * @author Marc André Audet <[email protected]>
49
 */
50
class Router
51
{
52
    /**
53
     * FastRoute, null if usingCache is set
54
     * @var RouteCollector
55
     */
56
    protected $routeCollector = null;
57
58
    /**
59
     * FastRoute cache file path.
60
     * @var string
61
     */
62
    protected $cacheFilePath;
63
64
    /**
65
     * List of middlewares called using the middleware() method.
66
     * @var array
67
     */
68
    protected $currentMiddlewares = [];
69
70
    /**
71
     * Initialize the route configurations.
72
     */
73
    public function __construct()
74
    {
75
        $config = _::getLibrary('Cervo/Config');
76
77
        $this->cacheFilePath = $config->get('Cervo/Application/Directory') . \DS . 'router.cache.php';
78
79
        $this->routeCollector = new RouteCollector(
80
            new RouteParser\Std(),
81
            new DataGenerator\GroupCountBased()
82
        );
83
84
        foreach (glob($config->get('Cervo/Application/Directory') . '*' . \DS . 'Router.php', \GLOB_NOSORT | \GLOB_NOESCAPE) as $file) {
85
86
            $function = require $file;
87
88
            if (is_callable($function)) {
89
                $function($this);
90
            }
91
92
        }
93
    }
94
95
    /**
96
     * Encapsulate all the routes that are added from $func(Router) with this middleware.
97
     *
98
     * @param array $middleware A middleware. The format is ['MyModule/MyLibrary', 'MyMethod'].
99
     * @param callable $func
100
     */
101
    public function middleware($middleware, $func)
102
    {
103
        if (is_array($middleware) && count($middleware) == 2) {
104
105
            array_push($this->currentMiddlewares, $middleware);
106
107
            $func($this);
108
109
            array_pop($this->currentMiddlewares);
110
111
        }
112
    }
113
114
    /**
115
     * Dispatch the request to the router.
116
     *
117
     * @return bool|Route
118
     */
119
    public function dispatch()
120
    {
121
        $dispatcher = $this->getDispatcher();
122
123
        if (defined('STDIN')) {
124
            $request_method = 'CLI';
125
        } else {
126
            $request_method = $_SERVER['REQUEST_METHOD'];
127
        }
128
129
        $routeInfo = $dispatcher->dispatch($request_method, $this->detectUri());
130
131
        if ($routeInfo[0] === Dispatcher::FOUND) {
132
133
            $handler = $routeInfo[1];
134
            $arguments = $routeInfo[2];
135
            $middlewares = $handler['middlewares'];
136
137
            if (is_array($middlewares)) {
138
                foreach ($middlewares as $middleware) {
139
140
                    if (is_array($middleware) && count($middleware) == 2) {
141
                        
142
                        $middleware_library = _::getLibrary($middleware[0]);
143
144
                        if (!$middleware_library->$middleware[1]($handler['parameters'], $arguments)) {
145
                            throw new RouteNotFoundException;
146
                        }
147
148
                    } else {
149
                        throw new InvalidMiddlewareException;
150
                    }
151
152
                }
153
            }
154
155
            return new Route($handler['method_path'], $handler['parameters'], $arguments);
156
157
        } else {
158
            throw new RouteNotFoundException;
159
        }
160
    }
161
162
    /**
163
     * Add a new route.
164
     *
165
     * @param string|array $http_method The HTTP method, example: GET, POST, PATCH, CLI, etc. Can be an array of values.
166
     * @param string $route The route
167
     * @param string $method_path The Method Path
168
     * @param array $middlewares Call a middleware before executing the route. The format is [['MyModule/MyLibrary', 'MyMethod'], ...]
169
     * @param array $parameters The parameters to pass
170
     */
171
    public function addRoute($http_method, $route, $method_path, $middlewares = [], $parameters = [])
172
    {
173
        if (_::getLibrary('Cervo/Config')->get('Production') == true && file_exists($this->cacheFilePath)) {
174
            return;
175
        }
176
177
        $this->routeCollector->addRoute($http_method, $route, [
0 ignored issues
show
Bug introduced by
It seems like $http_method defined by parameter $http_method on line 171 can also be of type array; however, FastRoute\RouteCollector::addRoute() does only seem to accept string|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
178
            'method_path' => $method_path,
179
            'middlewares' => array_merge($this->currentMiddlewares, $middlewares),
180
            'parameters' => $parameters
181
        ]);
182
    }
183
184
    protected function getDispatcher()
185
    {
186
        $dispatchData = null;
187
188
        if (_::getLibrary('Cervo/Config')->get('Production') == true && file_exists($this->cacheFilePath)) {
189
190
            $dispatchData = require $this->cacheFilePath;
191
192
            if (!is_array($dispatchData)) {
193
                throw new InvalidRouterCacheException;
194
            }
195
196
        } else {
197
            $dispatchData = $this->routeCollector->getData();
198
        }
199
200
        $dir = dirname($this->cacheFilePath);
201
202
        if (_::getLibrary('Cervo/Config')->get('Production') == true && !file_exists($this->cacheFilePath) && is_dir($dir) && is_writable($dir)) {
203
            file_put_contents(
204
                $this->cacheFilePath,
205
                '<?php return ' . var_export($dispatchData, true) . ';' . PHP_EOL,
206
                LOCK_EX
207
            );
208
        }
209
210
        return new Dispatcher\GroupCountBased($dispatchData);
211
    }
212
213
    /**
214
     * Returns a parsable URI
215
     *
216
     * @return string
217
     */
218
    protected function detectUri()
219
    {
220
        if (defined('STDIN')) {
221
            $args = array_slice($_SERVER['argv'], 1);
222
            return $args ? '/' . implode('/', $args) : '/';
223
        }
224
225
        if (!isset($_SERVER['REQUEST_URI']) || !isset($_SERVER['SCRIPT_NAME'])) {
226
            return '/';
227
        }
228
229
        $parts = preg_split('#\?#i', $this->getBaseUri(), 2);
230
        $uri = $parts[0];
231
232
        if ($uri == '/' || strlen($uri) <= 0) {
233
            return '/';
234
        }
235
236
        $uri = parse_url($uri, PHP_URL_PATH);
237
        return '/' . str_replace(['//', '../', '/..'], '/', trim($uri, '/'));
238
    }
239
240
    /**
241
     * Return the base URI for a request
242
     *
243
     * @return string
244
     */
245
    protected function getBaseUri()
246
    {
247
        $uri = $_SERVER['REQUEST_URI'];
248
249
        if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) {
250
            $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
251
        } elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) {
252
            $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
253
        }
254
255
        return $uri;
256
    }
257
}
258