Completed
Push — master ( 51ba5c...ecb964 )
by Anton
9s
created

Router::getBaseUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
ccs 2
cts 2
cp 1
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 = [];
73
74
    /**
75
     * @var array instance raw parameters
76
     */
77
    protected $rawParams = [];
78
79
    /**
80
     * @var array routers map
81
     */
82
    protected $routers = [];
83
84
    /**
85
     * @var array reverse map
86
     */
87
    protected $reverse = [];
88
89
    /**
90
     * Constructor of Router
91
     */
92 628
    public function __construct()
93
    {
94 628
        $routers = Cache::get('router:routers');
95 628
        $reverse = Cache::get('router:reverse');
96
97 628
        if (!$routers || !$reverse) {
98 628
            $routers = [];
99 628
            $reverse = [];
100 628
            $path = Application::getInstance()->getPath() . '/modules/*/controllers/*.php';
101 628
            foreach (new \GlobIterator($path) as $file) {
102
                /* @var \SplFileInfo $file */
103 628
                $module = $file->getPathInfo()->getPathInfo()->getBasename();
104 628
                $controller = $file->getBasename('.php');
105 628
                $controllerInstance = new Controller($module, $controller);
106 628
                $reflection = $controllerInstance->getReflection();
107 628
                if ($routes = $reflection->getRoute()) {
108 628
                    foreach ($routes as $route => $pattern) {
109 628
                        if (!isset($reverse[$module])) {
110 628
                            $reverse[$module] = [];
111
                        }
112
113 628
                        $reverse[$module][$controller] = ['route' => $route, 'params' => $reflection->getParams()];
114
115
                        $rule = [
116
                            $route => [
117 628
                                'pattern' => $pattern,
118 628
                                'module' => $module,
119 628
                                'controller' => $controller,
120 628
                                'params' => $reflection->getParams()
121
                            ]
122
                        ];
123
124
                        // static routers should be first
125 628
                        if (strpos($route, '$')) {
126 628
                            $routers = array_merge($routers, $rule);
127
                        } else {
128 628
                            $routers = array_merge($rule, $routers);
129
                        }
130
                    }
131
                }
132
            }
133 628
            Cache::set('router:routers', $routers);
134 628
            Cache::set('router:reverse', $reverse);
135
        }
136
137 628
        $this->routers = $routers;
138 628
        $this->reverse = $reverse;
139 628
    }
140
141
    /**
142
     * Get the base URL.
143
     *
144
     * @return string
145
     */
146 36
    public function getBaseUrl()
147
    {
148 36
        return $this->baseUrl;
149
    }
150
151
    /**
152
     * Set the base URL.
153
     *
154
     * @param  string $baseUrl
155
     * @return void
156
     */
157 628
    public function setBaseUrl($baseUrl)
158
    {
159 628
        $this->baseUrl = rtrim($baseUrl, '/') . '/';
160 628
    }
161
162
    /**
163
     * Get an action parameter
164
     *
165
     * @param  string $key
166
     * @param  mixed  $default Default value to use if key not found
167
     * @return mixed
168
     */
169
    public function getParam($key, $default = null)
170
    {
171
        return $this->params[$key] ?? $default;
172
    }
173
174
    /**
175
     * Set an action parameter
176
     *
177
     * A $value of null will unset the $key if it exists
178
     *
179
     * @param  string $key
180
     * @param  mixed  $value
181
     * @return void
182
     */
183 1
    public function setParam($key, $value)
184
    {
185 1
        $key = (string)$key;
186
187 1
        if ((null === $value) && isset($this->params[$key])) {
188
            unset($this->params[$key]);
189 1
        } elseif (null !== $value) {
190 1
            $this->params[$key] = $value;
191
        }
192 1
    }
193
194
    /**
195
     * Get parameters
196
     *
197
     * @return array
198
     */
199
    public function getParams()
200
    {
201
        return $this->params;
202
    }
203
204
    /**
205
     * Get raw params, w/out module and controller
206
     *
207
     * @return array
208
     */
209
    public function getRawParams()
210
    {
211
        return $this->rawParams;
212
    }
213
214
    /**
215
     * Get default module
216
     *
217
     * @return string
218
     */
219 39
    public function getDefaultModule()
220
    {
221 39
        return $this->defaultModule;
222
    }
223
224
    /**
225
     * Set default module
226
     *
227
     * @param  string $defaultModule
228
     * @return void
229
     */
230
    public function setDefaultModule($defaultModule)
231
    {
232
        $this->defaultModule = $defaultModule;
233
    }
234
235
    /**
236
     * Get default controller
237
     *
238
     * @return string
239
     */
240 39
    public function getDefaultController()
241
    {
242 39
        return $this->defaultController;
243
    }
244
245
    /**
246
     * Set default controller
247
     *
248
     * @param  string $defaultController
249
     * @return void
250
     */
251
    public function setDefaultController($defaultController)
252
    {
253
        $this->defaultController = $defaultController;
254
    }
255
256
    /**
257
     * Get error module
258
     *
259
     * @return string
260
     */
261 1
    public function getErrorModule()
262
    {
263 1
        return $this->errorModule;
264
    }
265
266
    /**
267
     * Set error module
268
     *
269
     * @param  string $errorModule
270
     * @return void
271
     */
272
    public function setErrorModule($errorModule)
273
    {
274
        $this->errorModule = $errorModule;
275
    }
276
277
    /**
278
     * Get error controller
279
     *
280
     * @return string
281
     */
282 1
    public function getErrorController()
283
    {
284 1
        return $this->errorController;
285
    }
286
287
    /**
288
     * Set error controller
289
     *
290
     * @param  string $errorController
291
     * @return void
292
     */
293
    public function setErrorController($errorController)
294
    {
295
        $this->errorController = $errorController;
296
    }
297
298
    /**
299
     * Build URL to controller
300
     *
301
     * @param  string $module
302
     * @param  string $controller
303
     * @param  array  $params
304
     * @return string
305
     */
306 30
    public function getUrl($module = self::DEFAULT_MODULE, $controller = self::DEFAULT_CONTROLLER, $params = [])
307
    {
308 30
        if (is_null($module)) {
309 1
            $module = Request::getModule();
310
        }
311
312 30
        if (is_null($controller)) {
313 1
            $controller = Request::getController();
314
        }
315
316 30
        if (empty($this->routers)) {
317
            return $this->urlRoute($module, $controller, $params);
318
        } else {
319 30
            if (isset($this->reverse[$module], $this->reverse[$module][$controller])) {
320 5
                return $this->urlCustom($module, $controller, $params);
321
            }
322 25
            return $this->urlRoute($module, $controller, $params);
323
        }
324
    }
325
326
    /**
327
     * Build full URL to controller
328
     *
329
     * @param  string $module
330
     * @param  string $controller
331
     * @param  array  $params
332
     * @return string
333
     */
334 1
    public function getFullUrl(
335
        $module = self::DEFAULT_MODULE,
336
        $controller = self::DEFAULT_CONTROLLER,
337
        $params = []
338
    ) {
339 1
        $scheme = Request::getInstance()->getUri()->getScheme() . '://';
340 1
        $host = Request::getInstance()->getUri()->getHost();
341 1
        $url = $this->getUrl($module, $controller, $params);
342 1
        return $scheme . $host . $url;
343
    }
344
345
    /**
346
     * Build URL by custom route
347
     *
348
     * @param  string $module
349
     * @param  string $controller
350
     * @param  array  $params
351
     * @return string
352
     */
353 5
    protected function urlCustom($module, $controller, $params)
354
    {
355 5
        $url = $this->reverse[$module][$controller]['route'];
356
357 5
        $getParams = [];
358 5
        foreach ($params as $key => $value) {
359
            // sub-array as GET params
360 4
            if (is_array($value)) {
361 1
                $getParams[$key] = $value;
362 1
                continue;
363
            }
364 3
            $url = str_replace('{$' . $key . '}', $value, $url, $replaced);
365
            // if not replaced, setup param as GET
366 3
            if (!$replaced) {
367 3
                $getParams[$key] = $value;
368
            }
369
        }
370
        // clean optional params
371 5
        $url = preg_replace('/\{\$[a-z0-9-_]+\}/i', '', $url);
372
        // clean regular expression (.*)
373 5
        $url = preg_replace('/\(\.\*\)/i', '', $url);
374
        // replace "//" with "/"
375 5
        $url = str_replace('//', '/', $url);
376
377 5
        if (!empty($getParams)) {
378 1
            $url .= '?' . http_build_query($getParams);
379
        }
380 5
        return $this->getBaseUrl() . ltrim($url, '/');
381
    }
382
383
    /**
384
     * Build URL by default route
385
     *
386
     * @param  string $module
387
     * @param  string $controller
388
     * @param  array  $params
389
     * @return string
390
     */
391 25
    protected function urlRoute($module, $controller, $params)
392
    {
393 25
        $url = $this->getBaseUrl();
394
395 25
        if (empty($params)) {
396 13
            if ($controller == self::DEFAULT_CONTROLLER) {
397 8
                if ($module == self::DEFAULT_MODULE) {
398 5
                    return $url;
399
                } else {
400 3
                    return $url . $module;
401
                }
402
            }
403
        }
404
405 18
        $url .= $module . '/' . $controller;
406 18
        $getParams = [];
407 18
        foreach ($params as $key => $value) {
408
            // sub-array as GET params
409 13
            if (is_array($value)) {
410 1
                $getParams[$key] = $value;
411 1
                continue;
412
            }
413 12
            $url .= '/' . urlencode($key) . '/' . urlencode($value);
414
        }
415 18
        if (!empty($getParams)) {
416 1
            $url .= '?' . http_build_query($getParams);
417
        }
418 18
        return $url;
419
    }
420
421
    /**
422
     * Process routing
423
     *
424
     * @return \Bluz\Router\Router
425
     */
426 2
    public function process()
427
    {
428
        switch (true) {
429
            // try process default router
430 2
            case $this->processDefault():
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
431 1
                break;
432
            // try process custom routers
433 1
            case $this->processCustom():
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
434
                break;
435
            // try process router
436 1
            case $this->processRoute():
437 1
                break;
438
        }
439
440 2
        $this->resetRequest();
441 2
        return $this;
442
    }
443
444
    /**
445
     * Process default router
446
     *
447
     * @return bool
448
     */
449 2
    protected function processDefault()
450
    {
451 2
        $uri = $this->getCleanUri();
452 2
        return empty($uri);
453
    }
454
455
    /**
456
     * Process custom router
457
     *
458
     * @return bool
459
     */
460 1
    protected function processCustom()
461
    {
462 1
        $uri = '/' . $this->getCleanUri();
463 1
        foreach ($this->routers as $router) {
464 1
            if (preg_match($router['pattern'], $uri, $matches)) {
465
                $this->setParam('_module', $router['module']);
466
                $this->setParam('_controller', $router['controller']);
467
468
                foreach ($router['params'] as $param => $type) {
469
                    if (isset($matches[$param])) {
470
                        $this->setParam($param, $matches[$param]);
471
                    }
472
                }
473 1
                return true;
474
            }
475
        }
476 1
        return false;
477
    }
478
479
    /**
480
     * Process router by default rules
481
     *
482
     * Default routers examples
483
     *     /
484
     *     /:module/
485
     *     /:module/:controller/
486
     *     /:module/:controller/:key1/:value1/:key2/:value2...
487
     *
488
     * @return bool
489
     */
490 1
    protected function processRoute()
491
    {
492 1
        $uri = $this->getCleanUri();
493 1
        $uri = trim($uri, '/');
494 1
        $raw = explode('/', $uri);
495
496
        // rewrite module from request
497 1
        if (sizeof($raw)) {
498 1
            $this->setParam('_module', array_shift($raw));
499
        }
500
        // rewrite module from controller
501 1
        if (sizeof($raw)) {
502 1
            $this->setParam('_controller', array_shift($raw));
503
        }
504 1
        if ($size = sizeof($raw)) {
505
            // save raw
506
            $this->rawParams = $raw;
507
508
            // save as index params
509
            foreach ($raw as $i => $value) {
510
                $this->setParam($i, $value);
511
            }
512
513
            // remove tail
514
            if ($size % 2 == 1) {
515
                array_pop($raw);
516
                $size = sizeof($raw);
517
            }
518
            // or use array_chunk and run another loop?
519
            for ($i = 0; $i < $size; $i = $i + 2) {
520
                $this->setParam($raw[$i], $raw[$i + 1]);
521
            }
522
        }
523 1
        return true;
524
    }
525
526
    /**
527
     * Reset Request
528
     *
529
     * @return void
530
     */
531 2
    protected function resetRequest()
532
    {
533 2
        $request = Request::getInstance();
534
535
        // priority:
536
        //  - default values
537
        //  - from GET query
538
        //  - from path
539 2
        $request = $request->withQueryParams(
540
            array_merge(
541
                [
542 2
                    '_module' => $this->getDefaultModule(),
543 2
                    '_controller' => $this->getDefaultController()
544
                ],
545 2
                $request->getQueryParams(),
546 2
                $this->params
547
            )
548
        );
549 2
        Request::setInstance($request);
550 2
    }
551
552
    /**
553
     * Get the request URI without baseUrl
554
     *
555
     * @return string
556
     */
557 2
    public function getCleanUri()
558
    {
559 2
        if ($this->cleanUri === null) {
560 2
            $uri = Request::getUri()->getPath();
561 2
            if ($this->getBaseUrl() && strpos($uri, $this->getBaseUrl()) === 0) {
562
                $uri = substr($uri, strlen($this->getBaseUrl()));
563
            }
564 2
            $this->cleanUri = $uri;
565
        }
566 2
        return $this->cleanUri;
567
    }
568
}
569