Passed
Push — 1.0.0-dev ( 93958a...e1c8ef )
by nguereza
02:26
created

Router::setLoggerFromParamOrCreateNewInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
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
		/**
233
	     * Set the route URI to use later
234
	     * @param string $uri the route URI, if is empty will determine automatically
235
	     * @return object
236
	     */
237
	    public function setRouteUri($uri = ''){
238
	    	$routeUri = '';
239
	    	if(! empty($uri)){
240
	    		$routeUri = $uri;
241
	    	}
242
	    	//if the application is running in CLI mode use the first argument
243
			else if(IS_CLI && isset($_SERVER['argv'][1])){
244
				$routeUri = $_SERVER['argv'][1];
245
			}
246
			else if(isset($_SERVER['REQUEST_URI'])){
247
				$routeUri = $_SERVER['REQUEST_URI'];
248
			}
249
			$this->logger->debug('Check if URL suffix is enabled in the configuration');
250
			//remove url suffix from the request URI
251
			$suffix = get_config('url_suffix');
252
			if ($suffix) {
253
				$this->logger->info('URL suffix is enabled in the configuration, the value is [' . $suffix . ']' );
254
				$routeUri = str_ireplace($suffix, '', $routeUri);
255
			} 
256
			if (strpos($routeUri, '?') !== false){
257
				$routeUri = substr($routeUri, 0, strpos($routeUri, '?'));
258
			}
259
			$this->uri = trim($routeUri, $this->uriTrim);
260
			return $this;
261
	    }
262
263
	     /**
264
		 * Set the route segments informations
265
		 * @param array $segements the route segments information
266
		 * 
267
		 * @return object
268
		 */
269
		public function setRouteSegments(array $segments = array()){
270
			if(! empty($segments)){
271
				$this->segments = $segments;
272
			} else if (!empty($this->uri)) {
273
				$this->segments = explode('/', $this->uri);
274
			}
275
			$segment = $this->segments;
276
			$baseUrl = get_config('base_url');
277
			//check if the app is not in DOCUMENT_ROOT
278
			if(isset($segment[0]) && stripos($baseUrl, $segment[0]) !== false){
279
				array_shift($segment);
280
				$this->segments = $segment;
281
			}
282
			$this->logger->debug('Check if the request URI contains the front controller');
283
			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...
284
				$this->logger->info('The request URI contains the front controller');
285
				array_shift($segment);
286
				$this->segments = $segment;
287
			}
288
			return $this;
289
		}
290
291
		/**
292
		 * Setting the route parameters like module, controller, method, argument
293
		 * @return object the current instance
294
		 */
295
		public function determineRouteParamsInformation() {
296
			$this->logger->debug('Routing process start ...');
297
			
298
			//determine route parameters using the config
299
			$this->determineRouteParamsFromConfig();
300
			
301
			//if can not determine the module/controller/method via the defined routes configuration we will use
302
			//the URL like http://domain.com/module/controller/method/arg1/arg2
303
			if(! $this->controller){
304
				$this->logger->info('Cannot determine the routing information using the predefined routes configuration, will use the request URI parameters');
305
				//determine route parameters using the REQUEST_URI param
306
				$this->determineRouteParamsFromRequestUri();
307
			}
308
			//Set the controller file path if not yet set
309
			$this->setControllerFilePath();
310
			$this->logger->debug('Routing process end.');
311
312
			return $this;
313
		}
314
	
315
		 /**
316
		 * Routing the request to the correspondant module/controller/method if exists
317
		 * otherwise send 404 error.
318
		 */
319
	    public function processRequest(){
320
	    	//Setting the route URI
321
			$this->setRouteUri();
322
323
			//setting route segments
324
			$this->setRouteSegments();
325
326
			$this->logger->info('The final Request URI is [' . implode('/', $this->segments) . ']' );
327
328
	    	//determine the route parameters information
329
	    	$this->determineRouteParamsInformation();
330
331
	    	$e404 = false;
332
	    	$classFilePath = $this->controllerPath;
333
	    	$controller = ucfirst($this->controller);
334
	    	$this->logger->info('The routing information are: module [' . $this->module . '], controller [' . $controller . '], method [' . $this->method . '], args [' . stringfy_vars($this->args) . ']');
335
	    	$this->logger->debug('Loading controller [' . $controller . '], the file path is [' . $classFilePath . ']...');
336
	    	
337
			if(file_exists($classFilePath)){
338
				require_once $classFilePath;
339
				if(! class_exists($controller, false)){
340
					$e404 = true;
341
					$this->logger->warning('The controller file [' .$classFilePath. '] exists but does not contain the class [' . $controller . ']');
342
				}
343
				else{
344
					$controllerInstance = new $controller();
345
					$controllerMethod = $this->getMethod();
346
					if(! method_exists($controllerInstance, $controllerMethod)){
347
						$e404 = true;
348
						$this->logger->warning('The controller [' . $controller . '] exist but does not contain the method [' . $controllerMethod . ']');
349
					}
350
					else{
351
						$this->logger->info('Routing data is set correctly now GO!');
352
						call_user_func_array(array($controllerInstance, $controllerMethod), $this->args);
353
						//render the final page to user
354
						$this->logger->info('Render the final output to the browser');
355
						get_instance()->response->renderFinalPage();
356
					}
357
				}
358
			}
359
			else{
360
				$this->logger->info('The controller file path [' . $classFilePath . '] does not exist');
361
				$e404 = true;
362
			}
363
			if($e404){
364
				if(IS_CLI){
365
					set_http_status_header(404);
366
					echo 'Error 404: page not found.';
367
				} else {
368
					$response =& class_loader('Response', 'classes');
369
					$response->send404();
370
				}
371
			}
372
	    }
373
374
375
	    /**
376
	    * Setting the route configuration using the configuration file and additional configuration from param
377
	    * @param array $overwriteConfig the additional configuration to overwrite with the existing one
378
	    * @param boolean $useConfigFile whether to use route configuration file
379
		* @return object
380
	    */
381
	    public function setRouteConfiguration(array $overwriteConfig = array(), $useConfigFile = true){
382
	        $route = array();
383
	        if ($useConfigFile && file_exists(CONFIG_PATH . 'routes.php')){
384
	            require_once CONFIG_PATH . 'routes.php';
385
	        }
386
	        $route = array_merge($route, $overwriteConfig);
387
	        $this->routes = $route;
388
	        //if route is empty remove all configuration
389
	        if(empty($route)){
390
	        	$this->removeAllRoute();
391
	        }
392
			return $this;
393
	    }
394
395
	     /**
396
		 * Get the route configuration
397
		 * @return array
398
		 */
399
		public function getRouteConfiguration(){
400
			return $this->routes;
401
		}
402
403
	    
404
	    /**
405
	     * Set the controller file path if is not set
406
	     * @param string $path the file path if is null will using the route 
407
	     * information
408
	     *
409
	     * @return object the current instance
410
	     */
411
	    public function setControllerFilePath($path = null){
412
	    	if($path !== null){
413
	    		$this->controllerPath = $path;
414
	    		return $this;
415
	    	}
416
	    	//did we set the controller, so set the controller path
417
			if($this->controller && ! $this->controllerPath){
418
				$this->logger->debug('Setting the file path for the controller [' . $this->controller . ']');
419
				$controllerPath = APPS_CONTROLLER_PATH . ucfirst($this->controller) . '.php';
420
				//if the controller is in module
421
				if($this->module){
422
					$path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
423
					if($path !== false){
424
						$controllerPath = $path;
425
					}
426
				}
427
				$this->controllerPath = $controllerPath;
428
			}
429
			return $this;
430
	    }
431
432
	    /**
433
	     * Determine the route parameters from route configuration
434
	     * @return void
435
	     */
436
	    protected function determineRouteParamsFromConfig(){
437
	    	$uri = implode('/', $this->segments);
438
	    	/*
439
	   		* Generics routes patterns
440
	    	*/
441
			$pattern = array(':num', ':alpha', ':alnum', ':any');
442
			$replace = array('[0-9]+', '[a-zA-Z]+', '[a-zA-Z0-9]+', '.*');
443
444
			$this->logger->debug(
445
									'Begin to loop in the predefined routes configuration ' 
446
									. 'to check if the current request match'
447
									);
448
449
			// Cycle through the URIs stored in the array
450
			foreach ($this->pattern as $index => $uriList) {
451
				$uriList = str_ireplace($pattern, $replace, $uriList);
452
				// Check for an existant matching URI
453
				if (preg_match("#^$uriList$#", $uri, $args)) {
454
					$this->logger->info(
455
										'Route found for request URI [' . $uri . '] using the predefined configuration '
456
										. ' [' . $this->pattern[$index] . '] --> [' . $this->callback[$index] . ']'
457
									);
458
					array_shift($args);
459
					//check if this contains an module
460
					$moduleControllerMethod = explode('#', $this->callback[$index]);
461
					if(is_array($moduleControllerMethod) && count($moduleControllerMethod) >= 2){
462
						$this->logger->info('The current request use the module [' . $moduleControllerMethod[0] . ']');
463
						$this->module = $moduleControllerMethod[0];
464
						$moduleControllerMethod = explode('@', $moduleControllerMethod[1]);
465
					}
466
					else{
467
						$this->logger->info('The current request does not use the module');
468
						$moduleControllerMethod = explode('@', $this->callback[$index]);
469
					}
470
					if(is_array($moduleControllerMethod)){
471
						if(isset($moduleControllerMethod[0])){
472
							$this->controller = $moduleControllerMethod[0];	
473
						}
474
						if(isset($moduleControllerMethod[1])){
475
							$this->method = $moduleControllerMethod[1];
476
						}
477
						$this->args = $args;
478
					}
479
					// stop here
480
					break;
481
				}
482
			}
483
484
			//first if the controller is not set and the module is set use the module name as the controller
485
			if(! $this->controller && $this->module){
486
				$this->logger->info(
487
									'After loop in predefined routes configuration, 
488
									the module name is set but the controller is not set, 
489
									so we will use module as the controller'
490
								);
491
				$this->controller = $this->module;
492
			}
493
	    }
494
495
	    /**
496
	     * Determine the route parameters using the server variable "REQUEST_URI"
497
	     * @return void
498
	     */
499
	    protected function determineRouteParamsFromRequestUri(){
500
	    	$segment = $this->segments;
501
	    	$nbSegment = count($segment);
502
			//if segment is null so means no need to perform
503
			if($nbSegment > 0){
504
				//get the module list
505
				$modules = Module::getModuleList();
506
				//first check if no module
507
				if(empty($modules)){
508
					$this->logger->info('No module was loaded will skip the module checking');
509
					//the application don't use module
510
					//controller
511
					if(isset($segment[0])){
512
						$this->controller = $segment[0];
513
						array_shift($segment);
514
					}
515
					//method
516
					if(isset($segment[0])){
517
						$this->method = $segment[0];
518
						array_shift($segment);
519
					}
520
					//args
521
					$this->args = $segment;
522
				}
523
				else{
524
					$this->logger->info('The application contains a loaded module will check if the current request is found in the module list');
525
					if(in_array($segment[0], $modules)){
526
						$this->logger->info('Found, the current request use the module [' . $segment[0] . ']');
527
						$this->module = $segment[0];
528
						array_shift($segment);
529
						//check if the second arg is the controller from module
530
						if(isset($segment[0])){
531
							$this->controller = $segment[0];
532
							//check if the request use the same module name and controller
533
							$path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
534
							if(! $path){
535
								$this->logger->info('The controller [' . $this->controller . '] not found in the module, may be will use the module [' . $this->module . '] as controller');
536
								$this->controller = $this->module;
537
							}
538
							else{
539
								$this->controllerPath = $path;
540
								array_shift($segment);
541
							}
542
						}
543
						//check for method
544
						if(isset($segment[0])){
545
							$this->method = $segment[0];
546
							array_shift($segment);
547
						}
548
						//the remaining is for args
549
						$this->args = $segment;
550
					}
551
					else{
552
						$this->logger->info('The current request information is not found in the module list');
553
						//controller
554
						if(isset($segment[0])){
555
							$this->controller = $segment[0];
556
							array_shift($segment);
557
						}
558
						//method
559
						if(isset($segment[0])){
560
							$this->method = $segment[0];
561
							array_shift($segment);
562
						}
563
						//args
564
						$this->args = $segment;
565
					}
566
				}
567
				if(! $this->controller && $this->module){
568
					$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');
569
					$this->controller = $this->module;
570
				}
571
			}
572
	    }
573
574
	    /**
575
	     * Set the route informations using the configuration
576
	     *
577
	     * @return object the current instance
578
	     */
579
	    protected function setRouteConfigurationInfos(){
580
	    	//adding route
581
			foreach($this->routes as $pattern => $callback){
582
				$this->add($pattern, $callback);
583
			}
584
			return $this;
585
		}
586
	}
587