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

Router::determineRouteParamsFromRequestUri()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 5
eloc 13
c 1
b 1
f 0
nc 5
nop 0
dl 0
loc 19
rs 9.5222
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
                    $response = & class_loader('Response', 'classes');
343
                    $response->send404();
344
                }
345
            }
346
        }
347
348
349
        /**
350
         * Setting the route configuration using the configuration file and additional configuration from param
351
         * @param array $overwriteConfig the additional configuration to overwrite with the existing one
352
         * @param boolean $useConfigFile whether to use route configuration file
353
         * @return object
354
         */
355
        public function setRouteConfiguration(array $overwriteConfig = array(), $useConfigFile = true) {
356
            $route = array();
357
            if ($useConfigFile && file_exists(CONFIG_PATH . 'routes.php')) {
358
                require_once CONFIG_PATH . 'routes.php';
359
            }
360
            $route = array_merge($route, $overwriteConfig);
361
            $this->routes = $route;
362
            //if route is empty remove all configuration
363
            if (empty($route)) {
364
                $this->removeAllRoute();
365
            }
366
            return $this;
367
        }
368
369
            /**
370
             * Get the route configuration
371
             * @return array
372
             */
373
        public function getRouteConfiguration() {
374
            return $this->routes;
375
        }
376
377
	    
378
        /**
379
         * Set the controller file path if is not set
380
         * @param string $path the file path if is null will using the route 
381
         * information
382
         *
383
         * @return object the current instance
384
         */
385
        public function setControllerFilePath($path = null) {
386
            if ($path !== null) {
387
                $this->controllerPath = $path;
388
                return $this;
389
            }
390
            //did we set the controller, so set the controller path
391
            if ($this->controller && !$this->controllerPath) {
392
                $this->logger->debug('Setting the file path for the controller [' . $this->controller . ']');
393
                $controllerPath = APPS_CONTROLLER_PATH . ucfirst($this->controller) . '.php';
394
                //if the controller is in module
395
                if ($this->module) {
396
                    $path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
397
                    if ($path !== false) {
398
                        $controllerPath = $path;
399
                    }
400
                }
401
                $this->controllerPath = $controllerPath;
402
            }
403
            return $this;
404
        }
405
406
        /**
407
         * Remove the DOCUMENT_ROOT and front controller from segments if exists
408
         * @return void
409
         */
410
        protected function removeDocumentRootFrontControllerFromSegments(){
411
            $segment = $this->segments;
412
            $baseUrl = get_config('base_url');
413
            //check if the app is not in DOCUMENT_ROOT
414
            if (isset($segment[0]) && stripos($baseUrl, $segment[0]) !== false) {
415
                array_shift($segment);
416
                $this->segments = $segment;
417
            }
418
            $this->logger->debug('Check if the request URI contains the front controller');
419
            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...
420
                $this->logger->info('The request URI contains the front controller');
421
                array_shift($segment);
422
                $this->segments = $segment;
423
            }
424
        }
425
426
         /**
427
         * Remove the URL suffix and query string values if exists
428
         * @param  string $uri the route URI to process
429
         * @return string      the final route uri after processed
430
         */
431
        protected function removeSuffixAndQueryStringFromUri($uri) {
432
            $this->logger->debug('Check if URL suffix is enabled in the configuration');
433
            //remove url suffix from the request URI
434
            $suffix = get_config('url_suffix');
435
            if ($suffix) {
436
                $this->logger->info('URL suffix is enabled in the configuration, the value is [' . $suffix . ']');
437
                $uri = str_ireplace($suffix, '', $uri);
438
            } 
439
            if (strpos($uri, '?') !== false) {
440
                $uri = substr($uri, 0, strpos($uri, '?'));
441
            }
442
            return $uri;
443
        }
444
445
        /**
446
         * Determine the route parameters from route configuration
447
         * @return void
448
         */
449
        protected function determineRouteParamsFromConfig() {
450
            $uri = implode('/', $this->segments);
451
            /*
452
	   		* Generics routes patterns
453
	    	*/
454
            $pattern = array(':num', ':alpha', ':alnum', ':any');
455
            $replace = array('[0-9]+', '[a-zA-Z]+', '[a-zA-Z0-9]+', '.*');
456
457
            $this->logger->debug(
458
                                    'Begin to loop in the predefined routes configuration ' 
459
                                    . 'to check if the current request match'
460
                                    );
461
            $args = array();
462
            $findIndex = -1;
463
            // Cycle through the URIs stored in the array
464
            foreach ($this->pattern as $index => $uriList) {
465
                $uriList = str_ireplace($pattern, $replace, $uriList);
466
                // Check for an existant matching URI
467
                if (preg_match("#^$uriList$#", $uri, $args)) {
468
                    $this->logger->info(
469
                                        'Route found for request URI [' . $uri . '] using the predefined configuration '
470
                                        . ' [' . $this->pattern[$index] . '] --> [' . $this->callback[$index] . ']'
471
                                    );
472
                    $findIndex = $index;
473
                    // stop here
474
                    break;
475
                }
476
            }
477
            if($findIndex !== -1){
478
                array_shift($args);
479
                //check if this contains an module
480
                $moduleControllerMethod = explode('#', $this->callback[$findIndex]);
481
                if (count($moduleControllerMethod) >= 2) {
482
                    $this->logger->info('The current request use the module [' . $moduleControllerMethod[0] . ']');
483
                    $this->module = $moduleControllerMethod[0];
484
                    $moduleControllerMethod = explode('@', $moduleControllerMethod[1]);
485
                } else {
486
                    $this->logger->info('The current request does not use the module');
487
                    $moduleControllerMethod = explode('@', $this->callback[$findIndex]);
488
                }
489
                if (count($moduleControllerMethod) >= 1) {
490
                    if (isset($moduleControllerMethod[0])) {
491
                        $this->controller = $moduleControllerMethod[0]; 
492
                    }
493
                    if (isset($moduleControllerMethod[1])) {
494
                        $this->method = $moduleControllerMethod[1];
495
                    }
496
                    $this->args = $args;
497
                }
498
            }
499
500
            //first if the controller is not set and the module is set use the module name as the controller
501
            if (!$this->controller && $this->module) {
502
                $this->logger->info(
503
                                    'After loop in predefined routes configuration, 
504
									the module name is set but the controller is not set, 
505
									so we will use module as the controller'
506
                                );
507
                $this->controller = $this->module;
508
            }
509
        }
510
511
512
        /**
513
         * Determine the route information if application does not have modules,
514
         * or the current request does not use module
515
         * @return void
516
         */
517
        protected function determineRouteParamsIfNoModuleOrNotFound(){
518
            $segment = $this->segments;
519
            //controller
520
            if (isset($segment[0])) {
521
                $this->controller = $segment[0];
522
                array_shift($segment);
523
            }
524
            //method
525
            if (isset($segment[0])) {
526
                $this->method = $segment[0];
527
                array_shift($segment);
528
            }
529
            //args
530
            $this->args = $segment;
531
        }
532
533
        /**
534
         * Find file path of the current controller using the current module
535
         * @return boolean true if the file path is found otherwise false.
536
         */
537
        protected function findModuleControllerFullPath(){
538
            $path = Module::findControllerFullPath(ucfirst($this->controller), $this->module);
539
            if (!$path) {
540
                $this->logger->info('The controller [' . $this->controller . '] not found in the module, may be will use the module [' . $this->module . '] as controller');
541
                $this->controller = $this->module;
542
                return false;
543
            }
544
            $this->controllerPath = $path;
545
            return true;
546
        }
547
548
        /**
549
         * Determine the route information if application have modules,
550
         * or the current request use module
551
         * @return void
552
         */
553
        protected function determineRouteParamsIfAppHaveModuleOrFound(){
554
            //get the module list
555
            $modules = Module::getModuleList();
556
            $segment = $this->segments;
557
558
            if (in_array($segment[0], $modules)) {
559
                $this->logger->info('Found, the current request use the module [' . $segment[0] . ']');
560
                $this->module = $segment[0];
561
                array_shift($segment);
562
                //check if the second arg is the controller from module
563
                if (isset($segment[0])) {
564
                    $this->controller = $segment[0];
565
566
                    //check if the request use the same module name and controller
567
                    if($this->findModuleControllerFullPath()){
568
                        array_shift($segment);
569
                    }
570
                }
571
                //check for method
572
                if (isset($segment[0])) {
573
                    $this->method = $segment[0];
574
                    array_shift($segment);
575
                }
576
                //the remaining is for args
577
                $this->args = $segment;
578
            } else {
579
                $this->logger->info('The current request information is not found in the module list');
580
                $this->determineRouteParamsIfNoModuleOrNotFound();
581
            }
582
        }
583
584
        /**
585
         * Determine the route parameters using the server variable "REQUEST_URI"
586
         * @return void
587
         */
588
        protected function determineRouteParamsFromRequestUri() {
589
            $segment = $this->segments;
590
            $nbSegment = count($segment);
591
            //if segment is null so means no need to perform
592
            if ($nbSegment > 0) {
593
                //get the module list
594
                $modules = Module::getModuleList();
595
596
                //first check if no module
597
                if (empty($modules)) {
598
                    $this->logger->info('No module was loaded will skip the module checking');
599
                    $this->determineRouteParamsIfNoModuleOrNotFound();
600
                } else {
601
                    $this->logger->info('The application contains a loaded module will check if the current request is found in the module list');
602
                    $this->determineRouteParamsIfAppHaveModuleOrFound();
603
                }
604
                if (!$this->controller && $this->module) {
605
                    $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');
606
                    $this->controller = $this->module;
607
                }
608
            }
609
        }
610
611
        /**
612
         * Set the route informations using the configuration
613
         *
614
         * @return object the current instance
615
         */
616
        protected function setRouteConfigurationInfos() {
617
            //adding route
618
            foreach ($this->routes as $pattern => $callback) {
619
                $this->add($pattern, $callback);
620
            }
621
            return $this;
622
        }
623
    }
624