Passed
Push — 1.0.0-dev ( 407604...83bedf )
by nguereza
03:26
created

Router::removeAllRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 6
rs 10
c 1
b 1
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 {
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
		 * The logger instance
92
		 * @var Log
93
		 */
94
		private $logger;
95
96
		/**
97
		 * Construct the new Router instance
98
		 */
99
		public function __construct(){
100
			$this->setLoggerFromParamOrCreateNewInstance(null);
101
			
102
			//loading routes for module
103
			$moduleRouteList = array();
104
			$modulesRoutes = Module::getModulesRoutes();
105
			if($modulesRoutes && is_array($modulesRoutes)){
106
				$moduleRouteList = $modulesRoutes;
107
				unset($modulesRoutes);
108
			}
109
			$this->setRouteConfiguration($moduleRouteList);
110
			$this->logger->info('The routes configuration are listed below: ' . stringfy_vars($this->routes));
111
112
			//Set route informations
113
			$this->setRouteConfigurationInfos();
114
		}
115
116
		/**
117
		 * Get the route patterns
118
		 * @return array
119
		 */
120
		public function getPattern(){
121
			return $this->pattern;
122
		}
123
124
		/**
125
		 * Get the route callbacks
126
		 * @return array
127
		 */
128
		public function getCallback(){
129
			return $this->callback;
130
		}
131
132
	    /**
133
		 * Get the module name
134
		 * @return string
135
		 */
136
		public function getModule(){
137
			return $this->module;
138
		}
139
		
140
		/**
141
		 * Get the controller name
142
		 * @return string
143
		 */
144
		public function getController(){
145
			return $this->controller;
146
		}
147
148
		/**
149
		 * Get the controller file path
150
		 * @return string
151
		 */
152
		public function getControllerPath(){
153
			return $this->controllerPath;
154
		}
155
156
		/**
157
		 * Get the controller method
158
		 * @return string
159
		 */
160
		public function getMethod(){
161
			return $this->method;
162
		}
163
164
		/**
165
		 * Get the request arguments
166
		 * @return array
167
		 */
168
		public function getArgs(){
169
			return $this->args;
170
		}
171
172
		/**
173
		 * Get the URL segments array
174
		 * @return array
175
		 */
176
		public function getSegments(){
177
			return $this->segments;
178
		}
179
180
		/**
181
	     * Return the Log instance
182
	     * @return Log
183
	     */
184
	    public function getLogger(){
185
	      return $this->logger;
186
	    }
187
188
	    /**
189
	     * Set the log instance
190
	     * @param Log $logger the log object
191
		 * @return object
192
	     */
193
	    public function setLogger($logger){
194
	      $this->logger = $logger;
195
	      return $this;
196
	    }
197
198
	    /**
199
		 * Get the route URI
200
		 * @return string
201
		 */
202
		public function getRouteUri(){
203
			return $this->uri;
204
		}
205
206
		/**
207
		* Add the URI and callback to the list of URIs to validate
208
		*
209
		* @param string $uri the request URI
210
		* @param object $callback the callback function
211
		*
212
		* @return object the current instance
213
		*/
214
		public function add($uri, $callback) {
215
			$uri = trim($uri, $this->uriTrim);
216
			if(in_array($uri, $this->pattern)){
217
				$this->logger->warning('The route [' . $uri . '] already added, may be adding again can have route conflict');
218
			}
219
			$this->pattern[] = $uri;
220
			$this->callback[] = $callback;
221
			return $this;
222
		}
223
224
		/**
225
		* Remove the route configuration
226
		*
227
		* @param string $uri the URI
228
		*
229
		* @return object the current instance
230
		*/
231
		public function removeRoute($uri) {
232
			$index  = array_search($uri, $this->pattern, true);
233
			if($index !== false){
234
				$this->logger->info('Remove route for uri [' . $uri . '] from the configuration');
235
				unset($this->pattern[$index]);
236
				unset($this->callback[$index]);
237
			}
238
			return $this;
239
		}
240
241
242
		/**
243
		* Remove all the routes from the configuration
244
		*
245
		* @return object the current instance
246
		*/
247
		public function removeAllRoute() {
248
			$this->logger->info('Remove all routes from the configuration');
249
			$this->pattern  = array();
250
			$this->callback = array();
251
			$this->routes = array();
252
			return $this;
253
		}
254
255
256
		/**
257
	     * Set the route URI to use later
258
	     * @param string $uri the route URI, if is empty will determine automatically
259
	     * @return object
260
	     */
261
	    public function setRouteUri($uri = ''){
262
	    	$routeUri = '';
263
	    	if(! empty($uri)){
264
	    		$routeUri = $uri;
265
	    	}
266
	    	//if the application is running in CLI mode use the first argument
267
			else if(IS_CLI){
268
				if(isset($_SERVER['argv'][1])){
269
					$routeUri = $_SERVER['argv'][1];
270
				}
271
			}
272
			else if(isset($_SERVER['REQUEST_URI'])){
273
				$routeUri = $_SERVER['REQUEST_URI'];
274
			}
275
			$this->logger->debug('Check if URL suffix is enabled in the configuration');
276
			//remove url suffix from the request URI
277
			$suffix = get_config('url_suffix');
278
			if ($suffix) {
279
				$this->logger->info('URL suffix is enabled in the configuration, the value is [' . $suffix . ']' );
280
				$routeUri = str_ireplace($suffix, '', $routeUri);
281
			} 
282
			if (strpos($routeUri, '?') !== false){
283
				$routeUri = substr($routeUri, 0, strpos($routeUri, '?'));
284
			}
285
			$this->uri = trim($routeUri, $this->uriTrim);
286
			return $this;
287
	    }
288
289
	     /**
290
		 * Set the route segments informations
291
		 * @param array $segements the route segments information
292
		 * 
293
		 * @return object
294
		 */
295
		public function setRouteSegments(array $segments = array()){
296
			if(! empty($segments)){
297
				$this->segments = $segments;
298
			} else if (!empty($this->uri)) {
299
				$this->segments = explode('/', $this->uri);
300
			}
301
			$segment = $this->segments;
302
			$baseUrl = get_config('base_url');
303
			//check if the app is not in DOCUMENT_ROOT
304
			if(isset($segment[0]) && stripos($baseUrl, $segment[0]) !== false){
305
				array_shift($segment);
306
				$this->segments = $segment;
307
			}
308
			$this->logger->debug('Check if the request URI contains the front controller');
309
			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...
310
				$this->logger->info('The request URI contains the front controller');
311
				array_shift($segment);
312
				$this->segments = $segment;
313
			}
314
			return $this;
315
		}
316
317
		/**
318
		 * Setting the route parameters like module, controller, method, argument
319
		 * @return object the current instance
320
		 */
321
		public function determineRouteParamsInformation() {
322
			$this->logger->debug('Routing process start ...');
323
			
324
			//determine route parameters using the config
325
			$this->determineRouteParamsFromConfig();
326
			
327
			//if can not determine the module/controller/method via the defined routes configuration we will use
328
			//the URL like http://domain.com/module/controller/method/arg1/arg2
329
			if(! $this->controller){
330
				$this->logger->info('Cannot determine the routing information using the predefined routes configuration, will use the request URI parameters');
331
				//determine route parameters using the REQUEST_URI param
332
				$this->determineRouteParamsFromRequestUri();
333
			}
334
			//Set the controller file path if not yet set
335
			$this->setControllerFilePath();
336
			$this->logger->debug('Routing process end.');
337
338
			return $this;
339
		}
340
	
341
		 /**
342
		 * Routing the request to the correspondant module/controller/method if exists
343
		 * otherwise send 404 error.
344
		 */
345
	    public function processRequest(){
346
	    	//Setting the route URI
347
			$this->setRouteUri();
348
349
			//setting route segments
350
			$this->setRouteSegments();
351
352
			$this->logger->info('The final Request URI is [' . implode('/', $this->segments) . ']' );
353
354
	    	//determine the route parameters information
355
	    	$this->determineRouteParamsInformation();
356
357
	    	$e404 = false;
358
	    	$classFilePath = $this->controllerPath;
359
	    	$controller = ucfirst($this->controller);
360
	    	$this->logger->info('The routing information are: module [' . $this->module . '], controller [' . $controller . '], method [' . $this->method . '], args [' . stringfy_vars($this->args) . ']');
361
	    	$this->logger->debug('Loading controller [' . $controller . '], the file path is [' . $classFilePath . ']...');
362
	    	
363
			if(file_exists($classFilePath)){
364
				require_once $classFilePath;
365
				if(! class_exists($controller, false)){
366
					$e404 = true;
367
					$this->logger->warning('The controller file [' .$classFilePath. '] exists but does not contain the class [' . $controller . ']');
368
				}
369
				else{
370
					$controllerInstance = new $controller();
371
					$controllerMethod = $this->getMethod();
372
					if(! method_exists($controllerInstance, $controllerMethod)){
373
						$e404 = true;
374
						$this->logger->warning('The controller [' . $controller . '] exist but does not contain the method [' . $controllerMethod . ']');
375
					}
376
					else{
377
						$this->logger->info('Routing data is set correctly now GO!');
378
						call_user_func_array(array($controllerInstance, $controllerMethod), $this->args);
379
						//render the final page to user
380
						$this->logger->info('Render the final output to the browser');
381
						get_instance()->response->renderFinalPage();
382
					}
383
				}
384
			}
385
			else{
386
				$this->logger->info('The controller file path [' . $classFilePath . '] does not exist');
387
				$e404 = true;
388
			}
389
			if($e404){
390
				if(IS_CLI){
391
					set_http_status_header(404);
392
					echo 'Error 404: page not found.';
393
				} else {
394
					$response =& class_loader('Response', 'classes');
395
					$response->send404();
396
				}
397
			}
398
	    }
399
400
401
	    /**
402
	    * Setting the route configuration using the configuration file and additional configuration from param
403
	    * @param array $overwriteConfig the additional configuration to overwrite with the existing one
404
	    * @param boolean $useConfigFile whether to use route configuration file
405
		* @return object
406
	    */
407
	    public function setRouteConfiguration(array $overwriteConfig = array(), $useConfigFile = true){
408
	        $route = array();
409
	        if ($useConfigFile && file_exists(CONFIG_PATH . 'routes.php')){
410
	            require_once CONFIG_PATH . 'routes.php';
411
	        }
412
	        $route = array_merge($route, $overwriteConfig);
413
	        $this->routes = $route;
414
	        //if route is empty remove all configuration
415
	        if(empty($route)){
416
	        	$this->removeAllRoute();
417
	        }
418
			return $this;
419
	    }
420
421
	     /**
422
		 * Get the route configuration
423
		 * @return array
424
		 */
425
		public function getRouteConfiguration(){
426
			return $this->routes;
427
		}
428
429
	    
430
	    /**
431
	     * Set the controller file path if is not set
432
	     * @param string $path the file path if is null will using the route 
433
	     * information
434
	     *
435
	     * @return object the current instance
436
	     */
437
	    public function setControllerFilePath($path = null){
438
	    	if($path !== null){
439
	    		$this->controllerPath = $path;
440
	    		return $this;
441
	    	}
442
	    	//did we set the controller, so set the controller path
443
			if($this->controller && ! $this->controllerPath){
444
				$this->logger->debug('Setting the file path for the controller [' . $this->controller . ']');
445
				$this->controllerPath = APPS_CONTROLLER_PATH . ucfirst($this->controller) . '.php';
446
				//if the controller is in module
447
				if($this->module){
448
					$this->controllerPath = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
0 ignored issues
show
Documentation Bug introduced by
It seems like Module::findControllerFu...roller), $this->module) can also be of type false. However, the property $controllerPath is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
449
				}
450
			}
451
			return $this;
452
	    }
453
454
	    /**
455
	     * Determine the route parameters from route configuration
456
	     * @return void
457
	     */
458
	    protected function determineRouteParamsFromConfig(){
459
	    	$uri = implode('/', $this->segments);
460
	    	/*
461
	   		* Generics routes patterns
462
	    	*/
463
			$pattern = array(':num', ':alpha', ':alnum', ':any');
464
			$replace = array('[0-9]+', '[a-zA-Z]+', '[a-zA-Z0-9]+', '.*');
465
466
			$this->logger->debug(
467
									'Begin to loop in the predefined routes configuration ' 
468
									. 'to check if the current request match'
469
									);
470
471
			// Cycle through the URIs stored in the array
472
			foreach ($this->pattern as $index => $uriList) {
473
				$uriList = str_ireplace($pattern, $replace, $uriList);
474
				// Check for an existant matching URI
475
				if (preg_match("#^$uriList$#", $uri, $args)) {
476
					$this->logger->info(
477
										'Route found for request URI [' . $uri . '] using the predefined configuration '
478
										. ' [' . $this->pattern[$index] . '] --> [' . $this->callback[$index] . ']'
479
									);
480
					array_shift($args);
481
					//check if this contains an module
482
					$moduleControllerMethod = explode('#', $this->callback[$index]);
483
					if(is_array($moduleControllerMethod) && count($moduleControllerMethod) >= 2){
484
						$this->logger->info('The current request use the module [' .$moduleControllerMethod[0]. ']');
485
						$this->module = $moduleControllerMethod[0];
486
						$moduleControllerMethod = explode('@', $moduleControllerMethod[1]);
487
					}
488
					else{
489
						$this->logger->info('The current request does not use the module');
490
						$moduleControllerMethod = explode('@', $this->callback[$index]);
491
					}
492
					if(is_array($moduleControllerMethod)){
493
						if(isset($moduleControllerMethod[0])){
494
							$this->controller = $moduleControllerMethod[0];	
495
						}
496
						if(isset($moduleControllerMethod[1])){
497
							$this->method = $moduleControllerMethod[1];
498
						}
499
						$this->args = $args;
500
					}
501
					// stop here
502
					break;
503
				}
504
			}
505
506
			//first if the controller is not set and the module is set use the module name as the controller
507
			if(! $this->controller && $this->module){
508
				$this->logger->info(
509
									'After loop in predefined routes configuration, 
510
									the module name is set but the controller is not set, 
511
									so we will use module as the controller'
512
								);
513
				$this->controller = $this->module;
514
			}
515
	    }
516
517
	    /**
518
	     * Determine the route parameters using the server variable "REQUEST_URI"
519
	     * @return void
520
	     */
521
	    protected function determineRouteParamsFromRequestUri(){
522
	    	$segment = $this->segments;
523
	    	$nbSegment = count($segment);
524
			//if segment is null so means no need to perform
525
			if($nbSegment > 0){
526
				//get the module list
527
				$modules = Module::getModuleList();
528
				//first check if no module
529
				if(empty($modules)){
530
					$this->logger->info('No module was loaded will skip the module checking');
531
					//the application don't use module
532
					//controller
533
					if(isset($segment[0])){
534
						$this->controller = $segment[0];
535
						array_shift($segment);
536
					}
537
					//method
538
					if(isset($segment[0])){
539
						$this->method = $segment[0];
540
						array_shift($segment);
541
					}
542
					//args
543
					$this->args = $segment;
544
				}
545
				else{
546
					$this->logger->info('The application contains a loaded module will check if the current request is found in the module list');
547
					if(in_array($segment[0], $modules)){
548
						$this->logger->info('Found, the current request use the module [' . $segment[0] . ']');
549
						$this->module = $segment[0];
550
						array_shift($segment);
551
						//check if the second arg is the controller from module
552
						if(isset($segment[0])){
553
							$this->controller = $segment[0];
554
							//check if the request use the same module name and controller
555
							$path = Module::findControllerFullPath(ucfirst($this->getController()), $this->getModule());
556
							if(! $path){
557
								$this->logger->info('The controller [' . $this->getController() . '] not found in the module, may be will use the module [' . $this->getModule() . '] as controller');
558
								$this->controller = $this->getModule();
559
							}
560
							else{
561
								$this->controllerPath = $path;
562
								array_shift($segment);
563
							}
564
						}
565
						//check for method
566
						if(isset($segment[0])){
567
							$this->method = $segment[0];
568
							array_shift($segment);
569
						}
570
						//the remaining is for args
571
						$this->args = $segment;
572
					}
573
					else{
574
						$this->logger->info('The current request information is not found in the module list');
575
						//controller
576
						if(isset($segment[0])){
577
							$this->controller = $segment[0];
578
							array_shift($segment);
579
						}
580
						//method
581
						if(isset($segment[0])){
582
							$this->method = $segment[0];
583
							array_shift($segment);
584
						}
585
						//args
586
						$this->args = $segment;
587
					}
588
				}
589
				if(! $this->controller && $this->module){
590
					$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');
591
					$this->controller = $this->module;
592
				}
593
			}
594
	    }
595
596
	    /**
597
	     * Set the route informations using the configuration
598
	     *
599
	     * @return object the current instance
600
	     */
601
	    protected function setRouteConfigurationInfos(){
602
	    	//adding route
603
			foreach($this->routes as $pattern => $callback){
604
				$this->add($pattern, $callback);
605
			}
606
			return $this;
607
		}
608
609
		/**
610
	     * Set the Log instance using argument or create new instance
611
	     * @param object $logger the Log instance if not null
612
	     */
613
	    protected function setLoggerFromParamOrCreateNewInstance(Log $logger = null){
614
	      if ($logger !== null){
615
	        $this->logger = $logger;
616
	      }
617
	      else{
618
	          $this->logger =& class_loader('Log', 'classes');
619
	          $this->logger->setLogger('Library::Router');
620
	      }
621
	    }
622
	}
623