Test Failed
Push — 1.0.0-dev ( 73dca1...204371 )
by nguereza
02:34
created

Router::setRouteUri()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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