Completed
Pull Request — master (#357)
by Anton
03:23
created

Router::getUrl()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

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