Passed
Push — 1.0.0-dev ( 4efac2...b68981 )
by nguereza
02:49
created

Router::setRouteParams()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 5
eloc 12
c 1
b 1
f 1
nc 16
nop 0
dl 0
loc 20
rs 9.5555
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
		/**
30
		* @var array $pattern: The list of URIs to validate against
31
		*/
32
		private $pattern = array();
33
34
		/**
35
		* @var array $callback: The list of callback to call
36
		*/
37
		private $callback = array();
38
39
		/**
40
		* @var string $uriTrim: The char to remove from the URIs
41
		*/
42
		protected $uriTrim = '/\^$';
43
44
		/**
45
		 * The module name of the current request
46
		 * @var string
47
		 */
48
		protected $module = null;
49
		
50
		/**
51
		 * The controller name of the current request
52
		 * @var string
53
		 */
54
		protected $controller = null;
55
56
		/**
57
		 * The controller path
58
		 * @var string
59
		 */
60
		protected $controllerPath = null;
61
62
		/**
63
		 * The method name. The default value is "index"
64
		 * @var string
65
		 */
66
		protected $method = 'index';
67
68
		/**
69
		 * List of argument to pass to the method
70
		 * @var array
71
		 */
72
		protected $args = array();
73
74
		/**
75
		 * List of routes configurations
76
		 * @var array
77
		 */
78
		protected $routes = array();
79
80
		/**
81
		 * The segments array for the current request
82
		 * @var array
83
		 */
84
		protected $segments;
85
86
		/**
87
		 * The logger instance
88
		 * @var Log
89
		 */
90
		private $logger;
91
92
		/**
93
		 * Construct the new Router instance
94
		 */
95
		public function __construct(){
96
			$this->setLoggerFromParamOrCreateNewInstance(null);
97
			
98
			//loading routes for module
99
			$moduleRouteList = array();
100
			$modulesRoutes = Module::getModulesRoutes();
101
			if($modulesRoutes && is_array($modulesRoutes)){
102
				$moduleRouteList = $modulesRoutes;
103
				unset($modulesRoutes);
104
			}
105
			$this->setRouteConfiguration($moduleRouteList);
106
			$this->logger->info('The routes configuration are listed below: ' . stringfy_vars($this->routes));
107
108
			//Set route parameters
109
			$this->setRouteParams();
110
		}
111
112
		/**
113
		* Add the URI and callback to the list of URIs to validate
114
		*
115
		* @param string $uri the request URI
116
		* @param object $callback the callback function
117
		*/
118
		public function add($uri, $callback) {
119
			$uri = trim($uri, $this->uriTrim);
120
			if(in_array($uri, $this->pattern)){
121
				$this->logger->warning('The route [' . $uri . '] already added, may be adding again can have route conflict');
122
			}
123
			$this->pattern[] = $uri;
124
			$this->callback[] = $callback;
125
		}
126
127
		/**
128
		 * Get the module name
129
		 * @return string
130
		 */
131
		public function getModule(){
132
			return $this->module;
133
		}
134
		
135
		/**
136
		 * Get the controller name
137
		 * @return string
138
		 */
139
		public function getController(){
140
			return $this->controller;
141
		}
142
143
		/**
144
		 * Get the controller file path
145
		 * @return string
146
		 */
147
		public function getControllerPath(){
148
			return $this->controllerPath;
149
		}
150
151
		/**
152
		 * Get the controller method
153
		 * @return string
154
		 */
155
		public function getMethod(){
156
			return $this->method;
157
		}
158
159
		/**
160
		 * Get the request arguments
161
		 * @return array
162
		 */
163
		public function getArgs(){
164
			return $this->args;
165
		}
166
167
		/**
168
		 * Get the URL segments array
169
		 * @return array
170
		 */
171
		public function getSegments(){
172
			return $this->segments;
173
		}
174
175
		/**
176
		 * Routing the request to the correspondant module/controller/method if exists
177
		 * otherwise send 404 error.
178
		 */
179
		public function run() {
180
			$benchmark =& class_loader('Benchmark');
181
			$benchmark->mark('ROUTING_PROCESS_START');
182
			$this->logger->debug('Routing process start ...');
183
			$segment = $this->segments;
184
			$baseUrl = get_config('base_url');
185
			//check if the app is not in DOCUMENT_ROOT
186
			if(isset($segment[0]) && stripos($baseUrl, $segment[0]) != false){
187
				array_shift($segment);
188
				$this->segments = $segment;
189
			}
190
			$this->logger->debug('Check if the request URI contains the front controller');
191
			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...
192
				$this->logger->info('The request URI contains the front controller');
193
				array_shift($segment);
194
				$this->segments = $segment;
195
			}
196
			else{
197
				$this->logger->info('The request URI does not contain the front controller');
198
			}
199
			$uri = implode('/', $segment);
200
			$this->logger->info('The final Request URI is [' . $uri . ']' );
201
			//generic routes
202
			$pattern = array(':num', ':alpha', ':alnum', ':any');
203
			$replace = array('[0-9]+', '[a-zA-Z]+', '[a-zA-Z0-9]+', '.*');
204
			$this->logger->debug('Begin to loop in the predefined routes configuration to check if the current request match');
205
			// Cycle through the URIs stored in the array
206
			foreach ($this->pattern as $index => $uriList) {
207
				$uriList = str_ireplace($pattern, $replace, $uriList);
208
				// Check for an existant matching URI
209
				if (preg_match("#^$uriList$#", $uri, $args)) {
210
					$this->logger->info('Route found for request URI [' . $uri . '] using the predefined configuration [' . $this->pattern[$index] . '] --> [' . $this->callback[$index] . ']');
211
					array_shift($args);
212
					//check if this contains an module
213
					$moduleControllerMethod = explode('#', $this->callback[$index]);
214
					if(is_array($moduleControllerMethod) && count($moduleControllerMethod) >= 2){
215
						$this->logger->info('The current request use the module [' .$moduleControllerMethod[0]. ']');
216
						$this->module = $moduleControllerMethod[0];
217
						$moduleControllerMethod = explode('@', $moduleControllerMethod[1]);
218
					}
219
					else{
220
						$this->logger->info('The current request does not use the module');
221
						$moduleControllerMethod = explode('@', $this->callback[$index]);
222
					}
223
					if(is_array($moduleControllerMethod)){
224
						if(isset($moduleControllerMethod[0])){
225
							$this->controller = $moduleControllerMethod[0];	
226
						}
227
						if(isset($moduleControllerMethod[1])){
228
							$this->method = $moduleControllerMethod[1];
229
						}
230
						$this->args = $args;
231
					}
232
					// stop here
233
					break;
234
				}
235
			}
236
			//first if the controller is not set and the module is set use the module name as the controller
237
			if(! $this->getController() && $this->getModule()){
238
				$this->logger->info('After loop in predefined routes configuration, the module name is set but the controller is not set, so we will use module as the controller');
239
				$this->controller = $this->getModule();
240
			}
241
			//if can not determine the module/controller/method via the defined routes configuration we will use
242
			//the URL like http://domain.com/module/controller/method/arg1/arg2
243
			if(! $this->getController()){
244
				$this->logger->info('Cannot determine the routing information using the predefined routes configuration, will use the request URI parameters');
245
				$nbSegment = count($segment);
246
				//if segment is null so means no need to perform
247
				if($nbSegment > 0){
248
					//get the module list
249
					$modules = Module::getModuleList();
250
					//first check if no module
251
					if(! $modules){
252
						$this->logger->info('No module was loaded will skip the module checking');
253
						//the application don't use module
254
						//controller
255
						if(isset($segment[0])){
256
							$this->controller = $segment[0];
257
							array_shift($segment);
258
						}
259
						//method
260
						if(isset($segment[0])){
261
							$this->method = $segment[0];
262
							array_shift($segment);
263
						}
264
						//args
265
						$this->args = $segment;
266
					}
267
					else{
268
						$this->logger->info('The application contains a loaded module will check if the current request is found in the module list');
269
						if(in_array($segment[0], $modules)){
270
							$this->logger->info('Found, the current request use the module [' . $segment[0] . ']');
271
							$this->module = $segment[0];
272
							array_shift($segment);
273
							//check if the second arg is the controller from module
274
							if(isset($segment[0])){
275
								$this->controller = $segment[0];
276
								//check if the request use the same module name and controller
277
								$path = Module::findControllerFullPath(ucfirst($this->getController()), $this->getModule());
278
								if(! $path){
279
									$this->logger->info('The controller [' . $this->getController() . '] not found in the module, may be will use the module [' . $this->getModule() . '] as controller');
280
									$this->controller = $this->getModule();
281
								}
282
								else{
283
									$this->controllerPath = $path;
284
									array_shift($segment);
285
								}
286
							}
287
							//check for method
288
							if(isset($segment[0])){
289
								$this->method = $segment[0];
290
								array_shift($segment);
291
							}
292
							//the remaining is for args
293
							$this->args = $segment;
294
						}
295
						else{
296
							$this->logger->info('The current request information is not found in the module list');
297
							//controller
298
							if(isset($segment[0])){
299
								$this->controller = $segment[0];
300
								array_shift($segment);
301
							}
302
							//method
303
							if(isset($segment[0])){
304
								$this->method = $segment[0];
305
								array_shift($segment);
306
							}
307
							//args
308
							$this->args = $segment;
309
						}
310
					}
311
				}
312
			}
313
			if(! $this->getController() && $this->getModule()){
314
				$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');
315
				$this->controller = $this->getModule();
316
			}
317
			//did we set the controller, so set the controller path
318
			if($this->getController() && ! $this->getControllerPath()){
319
				$this->logger->debug('Setting the file path for the controller [' . $this->getController() . ']');
320
				//if it is the module controller
321
				if($this->getModule()){
322
					$this->controllerPath = Module::findControllerFullPath(ucfirst($this->getController()), $this->getModule());
323
				}
324
				else{
325
					$this->controllerPath = APPS_CONTROLLER_PATH . ucfirst($this->getController()) . '.php';
326
				}
327
			}
328
			$controller = ucfirst($this->getController());
329
			$this->logger->info('The routing information are: module [' . $this->getModule() . '], controller [' . $controller . '], method [' . $this->getMethod() . '], args [' . stringfy_vars($this->args) . ']');
330
			$classFilePath = $this->getControllerPath();
331
			$this->logger->debug('Loading controller [' . $controller . '], the file path is [' . $classFilePath . ']...');
332
			$benchmark->mark('ROUTING_PROCESS_END');
333
			$e404 = false;
334
			if(file_exists($classFilePath)){
335
				require_once $classFilePath;
336
				if(! class_exists($controller, false)){
337
					$e404 = true;
338
					$this->logger->info('The controller file [' .$classFilePath. '] exists but does not contain the class [' . $controller . ']');
339
				}
340
				else{
341
					$controllerInstance = new $controller();
342
					$controllerMethod = $this->getMethod();
343
					if(! method_exists($controllerInstance, $controllerMethod)){
344
						$e404 = true;
345
						$this->logger->info('The controller [' . $controller . '] exist but does not contain the method [' . $controllerMethod . ']');
346
					}
347
					else{
348
						$this->logger->info('Routing data is set correctly now GO!');
349
						call_user_func_array(array($controllerInstance, $controllerMethod), $this->getArgs());
350
						$obj = & get_instance();
351
						//render the final page to user
352
						$this->logger->info('Render the final output to the browser');
353
						$obj->response->renderFinalPage();
354
					}
355
				}
356
			}
357
			else{
358
				$this->logger->info('The controller file path [' . $classFilePath . '] does not exist');
359
				$e404 = true;
360
			}
361
			if($e404){
362
				$response =& class_loader('Response', 'classes');
363
				$response->send404();
364
			}
365
		}
366
367
	/**
368
     * Return the Log instance
369
     * @return Log
370
     */
371
    public function getLogger(){
372
      return $this->logger;
373
    }
374
375
    /**
376
     * Set the log instance
377
     * @param Log $logger the log object
378
	 * @return object
379
     */
380
    public function setLogger($logger){
381
      $this->logger = $logger;
382
      return $this;
383
    }
384
385
    /**
386
    * Setting the route configuration using the configuration file and additional configuration from param
387
    * @param array $overwriteConfig the additional configuration to overwrite with the existing one
388
    * @param boolean $useConfigFile whether to use route configuration file
389
	* @return object
390
    */
391
    public function setRouteConfiguration(array $overwriteConfig = array(), $useConfigFile = true){
392
        $route = array();
393
        if ($useConfigFile && file_exists(CONFIG_PATH . 'routes.php')){
394
            require_once CONFIG_PATH . 'routes.php';
395
        }
396
        $route = array_merge($route, $overwriteConfig);
397
        $this->routes = $route;
398
		return $this;
399
    }
400
401
    /**
402
     * Set the route paramaters using the configuration
403
     */
404
    protected function setRouteParams(){
405
    	//adding route
406
		foreach($this->routes as $pattern => $callback){
407
			$this->add($pattern, $callback);
408
		}
409
		
410
		//here use directly the variable $_SERVER
411
		$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
412
		$this->logger->debug('Check if URL suffix is enabled in the configuration');
413
		//remove url suffix from the request URI
414
		$suffix = get_config('url_suffix');
415
		if ($suffix) {
416
			$this->logger->info('URL suffix is enabled in the configuration, the value is [' . $suffix . ']' );
417
			$uri = str_ireplace($suffix, '', $uri);
418
		} 
419
		if (strpos($uri, '?') !== false){
420
			$uri = substr($uri, 0, strpos($uri, '?'));
421
		}
422
		$uri = trim($uri, $this->uriTrim);
423
		$this->segments = explode('/', $uri);
424
	}
425
426
	/**
427
     * Set the Log instance using argument or create new instance
428
     * @param object $logger the Log instance if not null
429
     */
430
    protected function setLoggerFromParamOrCreateNewInstance(Log $logger = null){
431
      if ($logger !== null){
432
        $this->logger = $logger;
433
      }
434
      else{
435
          $this->logger =& class_loader('Log', 'classes');
436
          $this->logger->setLogger('Library::Router');
437
      }
438
    }
439
}
440