Completed
Push — master ( ffb215...f806ab )
by Anton
11s
created

Router::resetRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
ccs 9
cts 9
cp 1
rs 9.4285
cc 1
eloc 9
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link https://github.com/bluzphp/framework
7
 */
8
9
/**
10
 * @namespace
11
 */
12
namespace Bluz\Router;
13
14
use Bluz\Application\Application;
15
use Bluz\Common\Options;
16
use Bluz\Controller\Controller;
17
use Bluz\Proxy\Cache;
18
use Bluz\Proxy\Request;
19
20
/**
21
 * Router
22
 *
23
 * @package  Bluz\Router
24
 * @author   Anton Shevchuk
25
 * @link     https://github.com/bluzphp/framework/wiki/Router
26
 */
27
class Router
28
{
29
    use Options;
30
31
    /**
32
     * Or should be as properties?
33
     */
34
    const DEFAULT_MODULE = 'index';
35
    const DEFAULT_CONTROLLER = 'index';
36
    const ERROR_MODULE = 'error';
37
    const ERROR_CONTROLLER = 'index';
38
39
    /**
40
     * @var string base URL
41
     */
42
    protected $baseUrl;
43
44
    /**
45
     * @var string REQUEST_URI minus Base URL
46
     */
47
    protected $cleanUri;
48
49
    /**
50
     * @var string default module
51
     */
52
    protected $defaultModule = self::DEFAULT_MODULE;
53
54
    /**
55
     * @var string default Controller
56
     */
57
    protected $defaultController = self::DEFAULT_CONTROLLER;
58
59
    /**
60
     * @var string error module
61
     */
62
    protected $errorModule = self::ERROR_MODULE;
63
64
    /**
65
     * @var string error Controller
66
     */
67
    protected $errorController = self::ERROR_CONTROLLER;
68
69
    /**
70
     * @var array instance parameters
71
     */
72
    protected $params = array();
73
74
    /**
75
     * @var array instance raw parameters
76
     */
77
    protected $rawParams = array();
78
79
    /**
80
     * @var array routers map
81
     */
82
    protected $routers = array();
83
84
    /**
85
     * @var array reverse map
86
     */
87
    protected $reverse = array();
88
89
    /**
90
     * Constructor of Router
91
     */
92 630
    public function __construct()
93
    {
94 630
        $this->module = $this->getDefaultModule();
95 630
        $this->controller = $this->getDefaultController();
96
97 630
        $routers = Cache::get('router:routers');
98 630
        $reverse = Cache::get('router:reverse');
99
100 630
        if (!$routers || !$reverse) {
101 630
            $routers = array();
102 630
            $reverse = array();
103 630
            $path = Application::getInstance()->getPath() . '/modules/*/controllers/*.php';
104 630
            foreach (new \GlobIterator($path) as $file) {
105
                /* @var \SplFileInfo $file */
106 630
                $module = $file->getPathInfo()->getPathInfo()->getBasename();
107 630
                $controller = $file->getBasename('.php');
108 630
                $controllerInstance = new Controller($module, $controller);
109 630
                $reflection = $controllerInstance->getReflection();
110 630
                if ($routes = $reflection->getRoute()) {
111 630
                    foreach ($routes as $route => $pattern) {
112 630
                        if (!isset($reverse[$module])) {
113 630
                            $reverse[$module] = array();
114
                        }
115
116 630
                        $reverse[$module][$controller] = ['route' => $route, 'params' => $reflection->getParams()];
117
118
                        $rule = [
119
                            $route => [
120 630
                                'pattern' => $pattern,
121 630
                                'module' => $module,
122 630
                                'controller' => $controller,
123 630
                                'params' => $reflection->getParams()
124
                            ]
125
                        ];
126
127
                        // static routers should be first
128 630
                        if (strpos($route, '$')) {
129 630
                            $routers = array_merge($routers, $rule);
130
                        } else {
131 630
                            $routers = array_merge($rule, $routers);
132
                        }
133
                    }
134
                }
135
            }
136 630
            Cache::set('router:routers', $routers);
137 630
            Cache::set('router:reverse', $reverse);
138
        }
139
140 630
        $this->routers = $routers;
141 630
        $this->reverse = $reverse;
142 630
    }
143
144
    /**
145
     * Get the base URL.
146
     *
147
     * @return string
148
     */
149 36
    public function getBaseUrl()
150
    {
151 36
        return $this->baseUrl;
152
    }
153
154
    /**
155
     * Set the base URL.
156
     *
157
     * @param  string $baseUrl
158
     * @return void
159
     */
160 630
    public function setBaseUrl($baseUrl)
161
    {
162 630
        $this->baseUrl = rtrim($baseUrl, '/') . '/';
163 630
    }
164
165
    /**
166
     * Get an action parameter
167
     *
168
     * @param  string $key
169
     * @param  mixed  $default Default value to use if key not found
170
     * @return mixed
171
     */
172
    public function getParam($key, $default = null)
173
    {
174
        return isset($this->params[$key]) ? $this->params[$key] : $default;
175
    }
176
177
    /**
178
     * Set an action parameter
179
     *
180
     * A $value of null will unset the $key if it exists
181
     *
182
     * @param  string $key
183
     * @param  mixed  $value
184
     * @return void
185
     */
186 1
    public function setParam($key, $value)
187
    {
188 1
        $key = (string)$key;
189
190 1
        if ((null === $value) && isset($this->params[$key])) {
191
            unset($this->params[$key]);
192 1
        } elseif (null !== $value) {
193 1
            $this->params[$key] = $value;
194
        }
195 1
    }
196
197
    /**
198
     * Get parameters
199
     *
200
     * @return array
201
     */
202
    public function getParams()
203
    {
204
        return $this->params;
205
    }
206
207
    /**
208
     * Get raw params, w/out module and controller
209
     *
210
     * @return array
211
     */
212
    public function getRawParams()
213
    {
214
        return $this->rawParams;
215
    }
216
217
    /**
218
     * Get default module
219
     *
220
     * @return string
221
     */
222 630
    public function getDefaultModule()
223
    {
224 630
        return $this->defaultModule;
225
    }
226
227
    /**
228
     * Set default module
229
     *
230
     * @param  string $defaultModule
231
     * @return void
232
     */
233
    public function setDefaultModule($defaultModule)
234
    {
235
        $this->defaultModule = $defaultModule;
236
    }
237
238
    /**
239
     * Get default controller
240
     *
241
     * @return string
242
     */
243 630
    public function getDefaultController()
244
    {
245 630
        return $this->defaultController;
246
    }
247
248
    /**
249
     * Set default controller
250
     *
251
     * @param  string $defaultController
252
     * @return void
253
     */
254
    public function setDefaultController($defaultController)
255
    {
256
        $this->defaultController = $defaultController;
257
    }
258
259
    /**
260
     * Get error module
261
     *
262
     * @return string
263
     */
264 1
    public function getErrorModule()
265
    {
266 1
        return $this->errorModule;
267
    }
268
269
    /**
270
     * Set error module
271
     *
272
     * @param  string $errorModule
273
     * @return void
274
     */
275
    public function setErrorModule($errorModule)
276
    {
277
        $this->errorModule = $errorModule;
278
    }
279
280
    /**
281
     * Get error controller
282
     *
283
     * @return string
284
     */
285 1
    public function getErrorController()
286
    {
287 1
        return $this->errorController;
288
    }
289
290
    /**
291
     * Set error controller
292
     *
293
     * @param  string $errorController
294
     * @return void
295
     */
296
    public function setErrorController($errorController)
297
    {
298
        $this->errorController = $errorController;
299
    }
300
301
    /**
302
     * Build URL to controller
303
     *
304
     * @param  string $module
305
     * @param  string $controller
306
     * @param  array  $params
307
     * @return string
308
     */
309 30
    public function getUrl($module = self::DEFAULT_MODULE, $controller = self::DEFAULT_CONTROLLER, $params = array())
310
    {
311 30
        if (is_null($module)) {
312 1
            $module = Request::getModule();
313
        }
314
315 30
        if (is_null($controller)) {
316 1
            $controller = Request::getController();
317
        }
318
319 30
        if (empty($this->routers)) {
320
            return $this->urlRoute($module, $controller, $params);
321
        } else {
322 30
            if (isset($this->reverse[$module], $this->reverse[$module][$controller])) {
323 5
                return $this->urlCustom($module, $controller, $params);
324
            }
325 25
            return $this->urlRoute($module, $controller, $params);
326
        }
327
    }
328
329
    /**
330
     * Build full URL to controller
331
     *
332
     * @param  string $module
333
     * @param  string $controller
334
     * @param  array  $params
335
     * @return string
336
     */
337 1
    public function getFullUrl(
338
        $module = self::DEFAULT_MODULE,
339
        $controller = self::DEFAULT_CONTROLLER,
340
        $params = array()
341
    ) {
342 1
        $scheme = Request::getInstance()->getUri()->getScheme() . '://';
343 1
        $host = Request::getInstance()->getUri()->getHost();
344 1
        $url = $this->getUrl($module, $controller, $params);
345 1
        return $scheme . $host . $url;
346
    }
347
348
    /**
349
     * Build URL by custom route
350
     *
351
     * @param  string $module
352
     * @param  string $controller
353
     * @param  array  $params
354
     * @return string
355
     */
356 5
    protected function urlCustom($module, $controller, $params)
357
    {
358 5
        $url = $this->reverse[$module][$controller]['route'];
359
360 5
        $getParams = array();
361 5
        foreach ($params as $key => $value) {
362
            // sub-array as GET params
363 4
            if (is_array($value)) {
364 1
                $getParams[$key] = $value;
365 1
                continue;
366
            }
367 3
            $url = str_replace('{$' . $key . '}', $value, $url, $replaced);
368
            // if not replaced, setup param as GET
369 3
            if (!$replaced) {
370 3
                $getParams[$key] = $value;
371
            }
372
        }
373
        // clean optional params
374 5
        $url = preg_replace('/\{\$[a-z0-9-_]+\}/i', '', $url);
375
        // clean regular expression (.*)
376 5
        $url = preg_replace('/\(\.\*\)/i', '', $url);
377
        // replace "//" with "/"
378 5
        $url = str_replace('//', '/', $url);
379
380 5
        if (!empty($getParams)) {
381 1
            $url .= '?' . http_build_query($getParams);
382
        }
383 5
        return $this->getBaseUrl() . ltrim($url, '/');
384
    }
385
386
    /**
387
     * Build URL by default route
388
     *
389
     * @param  string $module
390
     * @param  string $controller
391
     * @param  array  $params
392
     * @return string
393
     */
394 25
    protected function urlRoute($module, $controller, $params)
395
    {
396 25
        $url = $this->getBaseUrl();
397
398 25
        if (empty($params)) {
399 13
            if ($controller == self::DEFAULT_CONTROLLER) {
400 8
                if ($module == self::DEFAULT_MODULE) {
401 5
                    return $url;
402
                } else {
403 3
                    return $url . $module;
404
                }
405
            }
406
        }
407
408 18
        $url .= $module . '/' . $controller;
409 18
        $getParams = array();
410 18
        foreach ($params as $key => $value) {
411
            // sub-array as GET params
412 13
            if (is_array($value)) {
413 1
                $getParams[$key] = $value;
414 1
                continue;
415
            }
416 12
            $url .= '/' . urlencode($key) . '/' . urlencode($value);
417
        }
418 18
        if (!empty($getParams)) {
419 1
            $url .= '?' . http_build_query($getParams);
420
        }
421 18
        return $url;
422
    }
423
424
    /**
425
     * Process routing
426
     *
427
     * @return \Bluz\Router\Router
428
     */
429 2
    public function process()
430
    {
431
        switch (true) {
432
            // try process default router
433 2
            case $this->processDefault():
434 1
                break;
435
            // try process custom routers
436 1
            case $this->processCustom():
437
                break;
438
            // try process router
439 1
            case $this->processRoute():
440 1
                break;
441
        }
442
443 2
        $this->resetRequest();
444 2
        return $this;
445
    }
446
447
    /**
448
     * Process default router
449
     *
450
     * @return bool
451
     */
452 2
    protected function processDefault()
453
    {
454 2
        $uri = $this->getCleanUri();
455 2
        return empty($uri);
456
    }
457
458
    /**
459
     * Process custom router
460
     *
461
     * @return bool
462
     */
463 1
    protected function processCustom()
464
    {
465 1
        $uri = '/' . $this->getCleanUri();
466 1
        foreach ($this->routers as $router) {
467 1
            if (preg_match($router['pattern'], $uri, $matches)) {
468
                $this->setParam('_module', $router['module']);
469
                $this->setParam('_controller', $router['controller']);
470
471
                foreach ($router['params'] as $param => $type) {
472
                    if (isset($matches[$param])) {
473
                        $this->setParam($param, $matches[$param]);
474
                    }
475
                }
476 1
                return true;
477
            }
478
        }
479 1
        return false;
480
    }
481
482
    /**
483
     * Process router by default rules
484
     *
485
     * Default routers examples
486
     *     /
487
     *     /:module/
488
     *     /:module/:controller/
489
     *     /:module/:controller/:key1/:value1/:key2/:value2...
490
     *
491
     * @return bool
492
     */
493 1
    protected function processRoute()
494
    {
495 1
        $uri = $this->getCleanUri();
496 1
        $uri = trim($uri, '/');
497 1
        $raw = explode('/', $uri);
498
499
        // rewrite module from request
500 1
        if (sizeof($raw)) {
501 1
            $this->setParam('_module', array_shift($raw));
502
        }
503
        // rewrite module from controller
504 1
        if (sizeof($raw)) {
505 1
            $this->setParam('_controller', array_shift($raw));
506
        }
507 1
        if ($size = sizeof($raw)) {
508
            // save raw
509
            $this->rawParams = $raw;
510
511
            // save as index params
512
            foreach ($raw as $i => $value) {
513
                $this->setParam($i, $value);
514
            }
515
516
            // remove tail
517
            if ($size % 2 == 1) {
518
                array_pop($raw);
519
                $size = sizeof($raw);
520
            }
521
            // or use array_chunk and run another loop?
522
            for ($i = 0; $i < $size; $i = $i + 2) {
523
                $this->setParam($raw[$i], $raw[$i + 1]);
524
            }
525
        }
526 1
        return true;
527
    }
528
529
    /**
530
     * Reset Request
531
     *
532
     * @return void
533
     */
534 2
    protected function resetRequest()
535
    {
536 2
        $request = Request::getInstance();
537
538
        // priority:
539
        //  - default values
540
        //  - from GET query
541
        //  - from path
542 2
        $request = $request->withQueryParams(
543
            array_merge(
544
                [
545 2
                    '_module' => $this->getDefaultModule(),
546 2
                    '_controller' => $this->getDefaultController()
547
                ],
548 2
                $request->getQueryParams(),
549 2
                $this->params
550
            )
551
        );
552 2
        Request::setInstance($request);
553 2
    }
554
555
    /**
556
     * Get the request URI without baseUrl
557
     *
558
     * @return string
559
     */
560 2
    public function getCleanUri()
561
    {
562 2
        if ($this->cleanUri === null) {
563 2
            $uri = Request::getUri()->getPath();
564 2
            if ($this->getBaseUrl() && strpos($uri, $this->getBaseUrl()) === 0) {
565
                $uri = substr($uri, strlen($this->getBaseUrl()));
566
            }
567 2
            $this->cleanUri = $uri;
568
        }
569 2
        return $this->cleanUri;
570
    }
571
}
572