Completed
Branch 4.0 (e514ad)
by Marc André
02:16
created

Router::handleMiddlewares()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 5
eloc 8
nc 4
nop 3
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\RouteMiddlewareFailedException;
39
use Cervo\Libraries\Exceptions\RouteNotFoundException;
40
use FastRoute\RouteCollector;
41
use FastRoute\RouteParser as RouteParser;
42
use FastRoute\DataGenerator as DataGenerator;
43
use FastRoute\Dispatcher as Dispatcher;
44
45
46
/**
47
 * Route manager for Cervo.
48
 *
49
 * @author Marc André Audet <[email protected]>
50
 */
51
class Router
52
{
53
    /**
54
     * FastRoute, null if usingCache is set
55
     * @var RouteCollector
56
     */
57
    protected $routeCollector = null;
58
59
    /**
60
     * FastRoute cache file path.
61
     * @var string
62
     */
63
    protected $cacheFilePath;
64
65
    /**
66
     * List of middlewares called using the middleware() method.
67
     * @var array
68
     */
69
    protected $currentMiddlewares = [];
70
71
    /**
72
     * Initialize the route configurations.
73
     */
74
    public function __construct()
75
    {
76
        $config = _::getLibrary('Cervo/Config');
77
78
        $this->cacheFilePath = $config->get('Cervo/Application/Directory') . \DS . 'router.cache.php';
79
80
        $this->routeCollector = new RouteCollector(
81
            new RouteParser\Std(),
82
            new DataGenerator\GroupCountBased()
83
        );
84
85
        foreach (glob($config->get('Cervo/Application/Directory') . '*' . \DS . 'Router.php', \GLOB_NOSORT | \GLOB_NOESCAPE) as $file) {
86
87
            $function = require $file;
88
89
            if (is_callable($function)) {
90
                $function($this);
91
            }
92
93
        }
94
    }
95
96
    /**
97
     * Encapsulate all the routes that are added from $func(Router) with this middleware.
98
     *
99
     * @param array $middleware A middleware. The format is ['MyModule/MyLibrary', 'MyMethod'].
100
     * @param callable $func
101
     */
102
    public function middleware($middleware, $func)
103
    {
104
        if (is_array($middleware) && count($middleware) == 2) {
105
106
            array_push($this->currentMiddlewares, $middleware);
107
108
            $func($this);
109
110
            array_pop($this->currentMiddlewares);
111
112
        }
113
    }
114
115
    /**
116
     * Dispatch the request to the router.
117
     *
118
     * @return bool|Route
119
     */
120
    public function dispatch()
121
    {
122
        $dispatcher = $this->getDispatcher();
123
124
        if (defined('STDIN')) {
125
            $request_method = 'CLI';
126
        } else {
127
            $request_method = $_SERVER['REQUEST_METHOD'];
128
        }
129
130
        $routeInfo = $dispatcher->dispatch($request_method, $this->detectUri());
131
132
        if ($routeInfo[0] === Dispatcher::FOUND) {
133
134
            $handler = $routeInfo[1];
135
            $arguments = $routeInfo[2];
136
            $middlewares = $handler['middlewares'];
137
138
            if (is_array($middlewares)) {
139
                $this->handleMiddlewares($middlewares, $handler['parameters'], $arguments);
140
            }
141
142
            return new Route($handler['method_path'], $handler['parameters'], $arguments);
143
144
        } else {
145
            throw new RouteNotFoundException;
146
        }
147
    }
148
149
    /**
150
     * Add a new route.
151
     *
152
     * @param string|string[] $http_method The HTTP method, example: GET, POST, PATCH, CLI, etc. Can be an array of values.
153
     * @param string $route The route
154
     * @param string $method_path The Method Path
155
     * @param array $parameters The parameters to pass
156
     */
157
    public function addRoute($http_method, $route, $method_path, $parameters = [])
158
    {
159
        if (_::getLibrary('Cervo/Config')->get('Production') == true && file_exists($this->cacheFilePath)) {
160
            return;
161
        }
162
163
        $this->routeCollector->addRoute($http_method, $route, [
164
            'method_path' => $method_path,
165
            'middlewares' => $this->currentMiddlewares,
166
            'parameters' => $parameters
167
        ]);
168
    }
169
170
    protected function getDispatcher()
171
    {
172
        $dispatchData = null;
173
174
        if (_::getLibrary('Cervo/Config')->get('Production') == true && file_exists($this->cacheFilePath)) {
175
176
            $dispatchData = require $this->cacheFilePath;
177
178
            if (!is_array($dispatchData)) {
179
                throw new InvalidRouterCacheException;
180
            }
181
182
        } else {
183
            $dispatchData = $this->routeCollector->getData();
184
        }
185
186
        $this->generateCache($dispatchData);
187
188
        return new Dispatcher\GroupCountBased($dispatchData);
189
    }
190
191
    protected function generateCache($dispatchData)
192
    {
193
        $dir = dirname($this->cacheFilePath);
194
195
        if (_::getLibrary('Cervo/Config')->get('Production') == true && !file_exists($this->cacheFilePath) && is_dir($dir) && is_writable($dir)) {
196
            file_put_contents(
197
                $this->cacheFilePath,
198
                '<?php return ' . var_export($dispatchData, true) . ';' . PHP_EOL,
199
                LOCK_EX
200
            );
201
        }
202
    }
203
204
    /**
205
     * Returns a parsable URI
206
     *
207
     * @return string
208
     */
209
    protected function detectUri()
210
    {
211
        if (defined('STDIN')) {
212
            $args = array_slice($_SERVER['argv'], 1);
213
            return $args ? '/' . implode('/', $args) : '/';
214
        }
215
216
        if (!isset($_SERVER['REQUEST_URI']) || !isset($_SERVER['SCRIPT_NAME'])) {
217
            return '/';
218
        }
219
220
        $parts = preg_split('#\?#i', $this->getBaseUri(), 2);
221
        $uri = $parts[0];
222
223
        if ($uri == '/' || strlen($uri) <= 0) {
224
            return '/';
225
        }
226
227
        $uri = parse_url($uri, PHP_URL_PATH);
228
        return '/' . str_replace(['//', '../', '/..'], '/', trim($uri, '/'));
229
    }
230
231
    /**
232
     * Return the base URI for a request
233
     *
234
     * @return string
235
     */
236
    protected function getBaseUri()
237
    {
238
        $uri = $_SERVER['REQUEST_URI'];
239
240
        if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0) {
241
            $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
242
        } elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0) {
243
            $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
244
        }
245
246
        return $uri;
247
    }
248
249
    /**
250
     * Throws an exception or return;
251
     *
252
     * @param $middlewares
253
     * @param $parameters
254
     * @param $arguments
255
     *
256
     * @return void
257
     */
258
    protected function handleMiddlewares($middlewares, $parameters, $arguments)
259
    {
260
        foreach ($middlewares as $middleware) {
261
262
            if (is_array($middleware) && count($middleware) == 2) {
263
264
                $middleware_library = _::getLibrary($middleware[0]);
265
266
                if (!$middleware_library->$middleware[1]($parameters, $arguments)) {
267
                    throw new RouteMiddlewareFailedException;
268
                }
269
270
            } else {
271
                throw new InvalidMiddlewareException;
272
            }
273
274
        }
275
    }
276
}
277