Passed
Push — 1.0.0-dev ( 247c52...b3c42a )
by nguereza
02:31
created

Router::setRouteParamsIfNoModuleOrNotFound()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 0
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
    defined('ROOT_PATH') or exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the GNU GPL License (GPL)
9
     *
10
     * Copyright (C) 2017 Tony NGUEREZA
11
     *
12
     * This program is free software; you can redistribute it and/or
13
     * modify it under the terms of the GNU General Public License
14
     * as published by the Free Software Foundation; either version 3
15
     * of the License, or (at your option) any later version.
16
     *
17
     * This program is distributed in the hope that it will be useful,
18
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
     * GNU General Public License for more details.
21
     *
22
     * You should have received a copy of the GNU General Public License
23
     * along with this program; if not, write to the Free Software
24
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25
     */
26
27
    class Router extends BaseClass {
28
        /**
29
         * @var array $pattern: The list of URIs to validate against
30
         */
31
        private $pattern = array();
32
33
        /**
34
         * @var array $callback: The list of callback to call
35
         */
36
        private $callback = array();
37
38
        /**
39
         * @var string $uriTrim: The char to remove from the URIs
40
         */
41
        protected $uriTrim = '/\^$';
42
43
        /**
44
         * @var string $uri: The route URI to use
45
         */
46
        protected $uri = '';
47
48
        /**
49
         * The module name of the current request
50
         * @var string
51
         */
52
        protected $module = null;
53
		
54
        /**
55
         * The controller name of the current request
56
         * @var string
57
         */
58
        protected $controller = null;
59
60
        /**
61
         * The controller path
62
         * @var string
63
         */
64
        protected $controllerPath = null;
65
66
        /**
67
         * The method name. The default value is "index"
68
         * @var string
69
         */
70
        protected $method = 'index';
71
72
        /**
73
         * List of argument to pass to the method
74
         * @var array
75
         */
76
        protected $args = array();
77
78
        /**
79
         * List of routes configurations
80
         * @var array
81
         */
82
        protected $routes = array();
83
84
        /**
85
         * The segments array for the current request
86
         * @var array
87
         */
88
        protected $segments = array();
89
90
        /**
91
         * Construct the new Router instance
92
         */
93
        public function __construct() {
94
            parent::__construct();
95
			
96
            //loading routes for module
97
            $moduleRouteList = array();
98
            $modulesRoutes = Module::getModulesRoutesConfig();
99
            if ($modulesRoutes && is_array($modulesRoutes)) {
100
                $moduleRouteList = $modulesRoutes;
101
                unset($modulesRoutes);
102
            }
103
            $this->setRouteConfiguration($moduleRouteList);
104
            $this->logger->info('The routes configuration are listed below: ' . stringfy_vars($this->routes));
105
106
            //Set route informations
107
            $this->setRouteConfigurationInfos();
108
        }
109
110
        /**
111
         * Get the route patterns
112
         * @return array
113
         */
114
        public function getPattern() {
115
            return $this->pattern;
116
        }
117
118
        /**
119
         * Get the route callbacks
120
         * @return array
121
         */
122
        public function getCallback() {
123
            return $this->callback;
124
        }
125
126
        /**
127
         * Get the module name
128
         * @return string
129
         */
130
        public function getModule() {
131
            return $this->module;
132
        }
133
		
134
        /**
135
         * Get the controller name
136
         * @return string
137
         */
138
        public function getController() {
139
            return $this->controller;
140
        }
141
142
        /**
143
         * Get the controller file path
144
         * @return string
145
         */
146
        public function getControllerPath() {
147
            return $this->controllerPath;
148
        }
149
150
        /**
151
         * Get the controller method
152
         * @return string
153
         */
154
        public function getMethod() {
155
            return $this->method;
156
        }
157
158
        /**
159
         * Get the request arguments
160
         * @return array
161
         */
162
        public function getArgs() {
163
            return $this->args;
164
        }
165
166
        /**
167
         * Get the URL segments array
168
         * @return array
169
         */
170
        public function getSegments() {
171
            return $this->segments;
172
        }
173
174
        /**
175
         * Get the route URI
176
         * @return string
177
         */
178
        public function getRouteUri() {
179
            return $this->uri;
180
        }
181
182
        /**
183
         * Add the URI and callback to the list of URIs to validate
184
         *
185
         * @param string $uri the request URI
186
         * @param string $callback the callback function
187
         *
188
         * @return object the current instance
189
         */
190
        public function add($uri, $callback) {
191
            $uri = trim($uri, $this->uriTrim);
192
            if (in_array($uri, $this->pattern)) {
193
                $this->logger->warning('The route [' . $uri . '] already added, may be adding again can have route conflict');
194
            }
195
            $this->pattern[] = $uri;
196
            $this->callback[] = $callback;
197
            return $this;
198
        }
199
200
        /**
201
         * Remove the route configuration
202
         *
203
         * @param string $uri the URI
204
         *
205
         * @return object the current instance
206
         */
207
        public function removeRoute($uri) {
208
            $index = array_search($uri, $this->pattern, true);
209
            if ($index !== false) {
210
                $this->logger->info('Remove route for uri [' . $uri . '] from the configuration');
211
                unset($this->pattern[$index]);
212
                unset($this->callback[$index]);
213
            }
214
            return $this;
215
        }
216
217
218
        /**
219
         * Remove all the routes from the configuration
220
         *
221
         * @return object the current instance
222
         */
223
        public function removeAllRoute() {
224
            $this->logger->info('Remove all routes from the configuration');
225
            $this->pattern  = array();
226
            $this->callback = array();
227
            $this->routes = array();
228
            return $this;
229
        }
230
231
        /**
232
         * Set the route URI to use later
233
         * @param string $uri the route URI, if is empty will determine automatically
234
         * @return object
235
         */
236
        public function setRouteUri($uri = '') {
237
            $routeUri = '';
238
            if (!empty($uri)) {
239
                $routeUri = $uri;
240
            }
241
            //if the application is running in CLI mode use the first argument
242
            else if (IS_CLI && isset($_SERVER['argv'][1])) {
243
                $routeUri = $_SERVER['argv'][1];
244
            } else if (isset($_SERVER['REQUEST_URI'])) {
245
                $routeUri = $_SERVER['REQUEST_URI'];
246
            }
247
            $routeUri = $this->removeSuffixAndQueryStringFromUri($routeUri);
248
            $this->uri = trim($routeUri, $this->uriTrim);
249
            return $this;
250
        }
251
252
        /**
253
         * Set the route segments informations
254
         * @param array $segements the route segments information
255
         * 
256
         * @return object
257
         */
258
        public function setRouteSegments(array $segments = array()) {
259
            if (!empty($segments)) {
260
                $this->segments = $segments;
261
            } else if (!empty($this->uri)) {
262
                $this->segments = explode('/', $this->uri);
263
            }
264
            $this->removeDocumentRootFrontControllerFromSegments();
265
            return $this;
266
        }
267
268
        /**
269
         * Setting the route parameters like module, controller, method, argument
270
         * @return object the current instance
271
         */
272
        public function determineRouteParamsInformation() {
273
            $this->logger->debug('Routing process start ...');
274
			
275
            //determine route parameters using the config
276
            $this->determineRouteParamsFromConfig();
277
			
278
            //if can not determine the module/controller/method via the defined routes configuration we will use
279
            //the URL like http://domain.com/module/controller/method/arg1/arg2
280
            if (!$this->controller) {
281
                $this->logger->info('Cannot determine the routing information using the predefined routes configuration, will use the request URI parameters');
282
                //determine route parameters using the route URI param
283
                $this->determineRouteParamsFromRequestUri();
284
            }
285
            //Set the controller file path if not yet set
286
            $this->setControllerFilePath();
287
            $this->logger->debug('Routing process end.');
288
289
            return $this;
290
        }
291
	
292
            /**
293
             * Routing the request to the correspondant module/controller/method if exists
294
             * otherwise send 404 error.
295
             */
296
        public function processRequest() {
297
            //Setting the route URI
298
            $this->setRouteUri();
299
300
            //setting route segments
301
            $this->setRouteSegments();
302
303
            $this->logger->info('The final Request URI is [' . implode('/', $this->segments) . ']');
304
305
            //determine the route parameters information
306
            $this->determineRouteParamsInformation();
307
308
            $e404 = false;
309
            $classFilePath = $this->controllerPath;
310
            $controller = ucfirst($this->controller);
311
            $this->logger->info('The routing information are: module [' . $this->module . '], controller [' . $controller . '], method [' . $this->method . '], args [' . stringfy_vars($this->args) . ']');
312
            $this->logger->debug('Loading controller [' . $controller . '], the file path is [' . $classFilePath . ']...');
313
	    	
314
            if (file_exists($classFilePath)) {
315
                require_once $classFilePath;
316
                if (!class_exists($controller, false)) {
317
                    $e404 = true;
318
                    $this->logger->warning('The controller file [' . $classFilePath . '] exists but does not contain the class [' . $controller . ']');
319
                } else {
320
                    $controllerInstance = new $controller();
321
                    $controllerMethod = $this->getMethod();
322
                    if (!method_exists($controllerInstance, $controllerMethod)) {
323
                        $e404 = true;
324
                        $this->logger->warning('The controller [' . $controller . '] exist but does not contain the method [' . $controllerMethod . ']');
325
                    } else {
326
                        $this->logger->info('Routing data is set correctly now GO!');
327
                        call_user_func_array(array($controllerInstance, $controllerMethod), $this->args);
328
                        //render the final page to user
329
                        $this->logger->info('Render the final output to the browser');
330
                        get_instance()->response->renderFinalPage();
331
                    }
332
                }
333
            } else {
334
                $this->logger->info('The controller file path [' . $classFilePath . '] does not exist');
335
                $e404 = true;
336
            }
337
            if ($e404) {
338
                if (IS_CLI) {
339
                    set_http_status_header(404);
340
                    echo 'Error 404: page not found.';
341
                } else {
342
                    //create the instance of Controller
343
                    $c404 = new Controller();
0 ignored issues
show
Unused Code introduced by
The assignment to $c404 is dead and can be removed.
Loading history...
344
                    get_instance()->response->render('404');
345
                    get_instance()->response->send404();
346
                }
347
            }
348
        }
349
350
351
        /**
352
         * Setting the route configuration using the configuration file and additional configuration from param
353
         * @param array $overwriteConfig the additional configuration to overwrite with the existing one
354
         * @param boolean $useConfigFile whether to use route configuration file
355
         * @return object
356
         */
357
        public function setRouteConfiguration(array $overwriteConfig = array(), $useConfigFile = true) {
358
            $route = array();
359
            if ($useConfigFile && file_exists(CONFIG_PATH . 'routes.php')) {
360
                require_once CONFIG_PATH . 'routes.php';
361
            }
362
            $route = array_merge($route, $overwriteConfig);
363
            $this->routes = $route;
364
            //if route is empty remove all configuration
365
            if (empty($route)) {
366
                $this->removeAllRoute();
367
            }
368
            return $this;
369
        }
370
371
            /**
372
             * Get the route configuration
373
             * @return array
374
             */
375
        public function getRouteConfiguration() {
376
            return $this->routes;
377
        }
378
379
	    
380
        /**
381
         * Set the controller file path if is not set
382
         * @param string $path the file path if is null will using the route 
383
         * information
384
         *
385
         * @return object the current instance
386
         */
387
        public function setControllerFilePath($path = null) {
388
            if ($path !== null) {
389
                $this->controllerPath = $path;
390
                return $this;
391
            }
392
            //did we set the controller, so set the controller path
393
            if ($this->controller && !$this->controllerPath) {
394
                $this->logger->debug('Setting the file path for the controller [' . $this->controller . ']');
395
                $controllerPath = APPS_CONTROLLER_PATH . ucfirst($this->controller) . '.php';
396
                //if the controller is in module
397
                if ($this->module) {
398
                    $path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
399
                    if ($path !== false) {
400
                        $controllerPath = $path;
401
                    }
402
                }
403
                $this->controllerPath = $controllerPath;
404
            }
405
            return $this;
406
        }
407
408
        /**
409
         * Remove the DOCUMENT_ROOT and front controller from segments if exists
410
         * @return void
411
         */
412
        protected function removeDocumentRootFrontControllerFromSegments(){
413
            $segment = $this->segments;
414
            $baseUrl = get_config('base_url');
415
            //check if the app is not in DOCUMENT_ROOT
416
            if (isset($segment[0]) && stripos($baseUrl, $segment[0]) !== false) {
417
                array_shift($segment);
418
                $this->segments = $segment;
419
            }
420
            $this->logger->debug('Check if the request URI contains the front controller');
421
            if (isset($segment[0]) && $segment[0] == SELF) {
0 ignored issues
show
Bug introduced by
The constant self was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
422
                $this->logger->info('The request URI contains the front controller');
423
                array_shift($segment);
424
                $this->segments = $segment;
425
            }
426
        }
427
428
         /**
429
         * Remove the URL suffix and query string values if exists
430
         * @param  string $uri the route URI to process
431
         * @return string      the final route uri after processed
432
         */
433
        protected function removeSuffixAndQueryStringFromUri($uri) {
434
            $this->logger->debug('Check if URL suffix is enabled in the configuration');
435
            //remove url suffix from the request URI
436
            $suffix = get_config('url_suffix');
437
            if ($suffix) {
438
                $this->logger->info('URL suffix is enabled in the configuration, the value is [' . $suffix . ']');
439
                $uri = str_ireplace($suffix, '', $uri);
440
            } 
441
            if (strpos($uri, '?') !== false) {
442
                $uri = substr($uri, 0, strpos($uri, '?'));
443
            }
444
            return $uri;
445
        }
446
447
        /**
448
         * Set the route params using the predefined config
449
         * @param int $findIndex the index in $this->callback
450
         * @param array $args      the argument of the request
451
         */
452
        protected function setRouteParamsUsingPredefinedConfig($findIndex, $args) {
453
            $moduleControllerMethod = explode('#', $this->callback[$findIndex]);
454
            if (count($moduleControllerMethod) >= 2) {
455
                $this->logger->info('The current request use the module [' . $moduleControllerMethod[0] . ']');
456
                $this->module = $moduleControllerMethod[0];
457
                $moduleControllerMethod = explode('@', $moduleControllerMethod[1]);
458
            } else {
459
                $this->logger->info('The current request does not use the module');
460
                $moduleControllerMethod = explode('@', $this->callback[$findIndex]);
461
            }
462
            if (count($moduleControllerMethod) >= 1) {
463
                if (isset($moduleControllerMethod[0])) {
464
                    $this->controller = $moduleControllerMethod[0]; 
465
                }
466
                if (isset($moduleControllerMethod[1])) {
467
                    $this->method = $moduleControllerMethod[1];
468
                }
469
                $this->args = $args;
470
            }
471
        }
472
473
        /**
474
         * Determine the route parameters from route configuration
475
         * @return void
476
         */
477
        protected function determineRouteParamsFromConfig() {
478
            $uri = implode('/', $this->segments);
479
            /*
480
	   		* Generics routes patterns
481
	    	*/
482
            $pattern = array(':num', ':alpha', ':alnum', ':any');
483
            $replace = array('[0-9]+', '[a-zA-Z]+', '[a-zA-Z0-9]+', '.*');
484
485
            $this->logger->debug(
486
                                    'Begin to loop in the predefined routes configuration ' 
487
                                    . 'to check if the current request match'
488
                                    );
489
            $args = array();
490
            $findIndex = -1;
491
            // Cycle through the URIs stored in the array
492
            foreach ($this->pattern as $index => $uriList) {
493
                $uriList = str_ireplace($pattern, $replace, $uriList);
494
                // Check for an existant matching URI
495
                if (preg_match("#^$uriList$#", $uri, $args)) {
496
                    $this->logger->info(
497
                                        'Route found for request URI [' . $uri . '] using the predefined configuration '
498
                                        . ' [' . $this->pattern[$index] . '] --> [' . $this->callback[$index] . ']'
499
                                    );
500
                    $findIndex = $index;
501
                    // stop here
502
                    break;
503
                }
504
            }
505
            if($findIndex !== -1){
506
                array_shift($args);
507
                $this->setRouteParamsUsingPredefinedConfig($findIndex, $args);
508
            }
509
510
            //first if the controller is not set and the module is set use the module name as the controller
511
            if (!$this->controller && $this->module) {
512
                $this->logger->info(
513
                                    'After loop in predefined routes configuration, 
514
									the module name is set but the controller is not set, 
515
									so we will use module as the controller'
516
                                );
517
                $this->controller = $this->module;
518
            }
519
        }
520
521
        /**
522
         * Find file path of the current controller using the current module
523
         * @return boolean true if the file path is found otherwise false.
524
         */
525
        protected function findControllerFullPathUsingCurrentModule(){
526
            $path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
527
            if (!$path) {
528
                $this->logger->info('The controller [' . $this->controller . '] not found in the module, may be will use the module [' . $this->module . '] as controller');
529
                $this->controller = $this->module;
530
                return false;
531
            }
532
            $this->controllerPath = $path;
533
            return true;
534
        }
535
536
537
538
        /**
539
         * Set the route information if application does not have modules,
540
         * or the current request does not use module
541
         * @return void
542
         */
543
        protected function setRouteParamsIfNoModuleOrNotFound(){
544
            $segment = $this->segments;
545
            //controller
546
            if (isset($segment[0])) {
547
                $this->controller = $segment[0];
548
                array_shift($segment);
549
            }
550
            //method
551
            if (isset($segment[0])) {
552
                $this->method = $segment[0];
553
                array_shift($segment);
554
            }
555
            //args
556
            $this->args = $segment;
557
        }
558
559
        /**
560
         * Set the route information if application have modules,
561
         * or the current request use module
562
         * @return void
563
         */
564
        protected function setRouteParamsIfAppHasModuleOrFound(){
565
            //get the module list
566
            $modules = Module::getModuleList();
567
            $segment = $this->segments;
568
569
            if (in_array($segment[0], $modules)) {
570
                $this->logger->info('Found, the current request use the module [' . $segment[0] . ']');
571
                $this->module = $segment[0];
572
                array_shift($segment);
573
                //check if the second arg is the controller from module
574
                if (isset($segment[0])) {
575
                    $this->controller = $segment[0];
576
577
                    //check if the request use the same module name and controller
578
                    if($this->findControllerFullPathUsingCurrentModule()){
579
                        array_shift($segment);
580
                    }
581
                }
582
                //check for method
583
                if (isset($segment[0])) {
584
                    $this->method = $segment[0];
585
                    array_shift($segment);
586
                }
587
                //the remaining is for args
588
                $this->args = $segment;
589
            } else {
590
                $this->logger->info('The current request information is not found in the module list');
591
                $this->setRouteParamsIfNoModuleOrNotFound();
592
            }
593
        }
594
595
        /**
596
         * Determine the route parameters using the server variable "REQUEST_URI"
597
         * @return void
598
         */
599
        protected function determineRouteParamsFromRequestUri() {
600
            $segment = $this->segments;
601
            $nbSegment = count($segment);
602
            //if segment is null so means no need to perform
603
            if ($nbSegment > 0) {
604
                //get the module list
605
                $modules = Module::getModuleList();
606
607
                //first check if no module
608
                if (empty($modules)) {
609
                    $this->logger->info('No module was loaded will skip the module checking');
610
                    $this->setRouteParamsIfNoModuleOrNotFound();
611
                } else {
612
                    $this->logger->info('The application contains a loaded module will check if the current request is found in the module list');
613
                    $this->setRouteParamsIfAppHasModuleOrFound();
614
                }
615
                if (!$this->controller && $this->module) {
616
                    $this->logger->info('After using the request URI the module name is set but the controller is not set so we will use module as the controller');
617
                    $this->controller = $this->module;
618
                }
619
            }
620
        }
621
622
        /**
623
         * Set the route informations using the configuration
624
         *
625
         * @return object the current instance
626
         */
627
        protected function setRouteConfigurationInfos() {
628
            //adding route
629
            foreach ($this->routes as $pattern => $callback) {
630
                $this->add($pattern, $callback);
631
            }
632
            return $this;
633
        }
634
    }
635