AloFramework documentation
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo

Namespaces

  • Alo
    • Cache
    • CLI
    • Controller
    • Db
      • Query
    • Exception
    • FileSystem
    • Session
    • Traversables
    • Validators
    • Windows
  • Controller
  • None
  • PHP

Classes

  • AbstractController
  • AbstractErrorController
  • Router
  1 <?php
  2 
  3     namespace Alo\Controller;
  4 
  5     use Alo;
  6     use Alo\Exception\ControllerException as CE;
  7     use ReflectionClass;
  8 
  9     if (!defined('GEN_START')) {
 10         http_response_code(404);
 11     } else {
 12 
 13         /**
 14          * Handles routing to the correct controller and method
 15          *
 16          * @author Art <a.molcanovas@gmail.com>
 17          */
 18         class Router {
 19 
 20             /**
 21              * Pretty self-explanatory, isn't it?
 22              *
 23              * @var string
 24              */
 25             const CONTROLLER_NAMESPACE = '\Controller\\';
 26 
 27             /**
 28              * Delimiter used in the regex checking
 29              *
 30              * @var string
 31              */
 32             const PREG_DELIMITER = '~';
 33             /**
 34              * Static reference to the last instance of the class
 35              *
 36              * @var Router
 37              */
 38             static $this;
 39             /**
 40              * Default params for a route
 41              *
 42              * @var array
 43              */
 44             protected static $routeDefaults = ['dir'    => null,
 45                                                'method' => 'index',
 46                                                'args'   => []];
 47             /**
 48              * The server name
 49              *
 50              * @var string
 51              */
 52             protected $serverName;
 53             /**
 54              * The server IP
 55              *
 56              * @var string
 57              */
 58             protected $serverAddr;
 59             /**
 60              * The port in use
 61              *
 62              * @var int
 63              */
 64             protected $port;
 65             /**
 66              * The remote address
 67              *
 68              * @var string
 69              */
 70             protected $remoteAddr;
 71             /**
 72              * The request scheme
 73              *
 74              * @var string
 75              */
 76             protected $requestScheme;
 77             /**
 78              * The raw path info
 79              *
 80              * @var string
 81              */
 82             protected $path;
 83             /**
 84              * Request method in use
 85              *
 86              * @var string
 87              */
 88             protected $requestMethod;
 89             /**
 90              * Directory name
 91              *
 92              * @var string
 93              */
 94             protected $dir;
 95             /**
 96              * Controller name
 97              *
 98              * @var string
 99              */
100             protected $controller;
101             /**
102              * Method name
103              *
104              * @var string
105              */
106             protected $method;
107             /**
108              * Arguments to pass on to the method
109              *
110              * @var array
111              */
112             protected $methodArgs = [];
113             /**
114              * The error controller name
115              *
116              * @var string
117              */
118             protected $errController;
119             /**
120              * The default controller
121              *
122              * @var string
123              */
124             protected $defaultController;
125             /**
126              * The routes array
127              *
128              * @var array
129              */
130             protected $routes = [];
131             /**
132              * Whether we're dealing with a CLI request...
133              *
134              * @var boolean
135              */
136             protected $isCliRequest;
137             /**
138              * Whether we're dealing with an AJAX request
139              *
140              * @var boolean
141              */
142             protected $isAjaxRequest;
143 
144             /**
145              * Initialises the router
146              *
147              * @author Art <a.molcanovas@gmail.com>
148              * @return Router
149              */
150             function init() {
151                 self::$this = &$this;
152 
153                 return $this->initNoCall()->tryCall();
154             }
155 
156             /**
157              * Tries to call the appropriate class' method
158              *
159              * @author Art <a.molcanovas@gmail.com>
160              * @return Router
161              * @throws CE If the controller is already the error controller
162              * @uses   self::forceError()
163              */
164             protected function tryCall() {
165                 /** @noinspection PhpUnusedLocalVariableInspection */
166                 $rc = $rm = $init = false;
167 
168                 try {
169                     $rc = new ReflectionClass(self::CONTROLLER_NAMESPACE . $this->controller);
170 
171                     //Must be abstract controller's subclass
172                     if (!$rc->isAbstract() && $rc->isSubclassOf('\Alo\Controller\AbstractController')) {
173                         $rm = $rc->getMethod($this->method);
174 
175                         //And a public method
176                         if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
177                             //Excellent. Instantiate!
178                             $init = true;
179                         } else {
180                             $this->forceError();
181                         }
182                     } else {
183                         $this->forceError();
184                     }
185                 } catch (\ReflectionException $ex) {
186                     $this->forceError($ex->getMessage());
187                 }
188 
189                 if ($init) {
190                     \Log::debug('Initialising controller ' .
191                                 $this->controller .
192                                 '->' .
193                                 $this->method .
194                                 '(' .
195                                 implode(',', $this->methodArgs) .
196                                 ')');
197                     $controllerName  = self::CONTROLLER_NAMESPACE . $this->controller;
198                     Alo::$controller = new $controllerName;
199                     call_user_func_array([Alo::$controller, $this->method], $this->methodArgs);
200                 }
201 
202                 return $this;
203             }
204 
205             /**
206              * Forces the error controller
207              *
208              * @author Art <a.molcanovas@gmail.com>
209              *
210              * @param string $msg Optionally, the error message thrown by ReflectionClass
211              *                    or ReflectionMethod
212              *
213              * @return \Alo\Controller\Router
214              * @throws CE If the controller is already the error controller
215              * @uses   self::tryCall()
216              */
217             protected function forceError($msg = null) {
218                 if ($this->controller != $this->errController) {
219                     \Log::debug('404\'d on path: ' .
220                                 $this->path .
221                                 '. Settings were as follows: dir: ' .
222                                 $this->dir .
223                                 ', class: ' .
224                                 $this->controller .
225                                 ', method: ' .
226                                 $this->method .
227                                 ', args: ' .
228                                 json_encode($this->methodArgs));
229 
230                     $path = DIR_CONTROLLERS . strtolower($this->errController) . '.php';
231                     if (file_exists($path)) {
232                         include_once $path;
233                     }
234 
235                     $this->controller = $this->errController;
236                     $this->method     = 'error';
237                     $this->methodArgs = [404];
238                     $this->tryCall();
239                 } else {
240                     throw new CE('No route available and the error controller ' .
241                                  'is invalid.' .
242                                  ($msg ? ' Exception message returned: ' . $msg : ''), CE::E_INVALID_ROUTE);
243                 }
244 
245                 return $this;
246             }
247 
248             /**
249              * Same as init(), but without attempting to call the controller
250              *
251              * @author Art <a.molcanovas@gmail.com>
252              *
253              * @throws CE When the config file is not found
254              * @throws CE When $error_controller_class is not present in the config file
255              * @throws CE When The default controller is not present in the config file
256              * @throws CE When $routes is not a valid array
257              * @throws CE When a route value is not an array.
258              * @return Router
259              */
260             function initNoCall() {
261                 $this->isCliRequest  = php_sapi_name() == 'cli' || defined('STDIN');
262                 $this->isAjaxRequest = \get($_SERVER['HTTP_X_REQUESTED_WITH']) == 'XMLHttpRequest';
263 
264                 return $this->initServerVars()->initPath()->initRoutes()->resolvePath();
265             }
266 
267             /**
268              * Resolves the controller/method path
269              *
270              * @author Art <a.molcanovas@gmail.com>
271              * @return Router
272              */
273             protected function resolvePath() {
274                 //Use the default controller if the path is unavailable
275                 if (!$this->path) {
276                     $filepath = DIR_CONTROLLERS . strtolower($this->defaultController) . '.php';
277 
278                     if (file_exists($filepath)) {
279                         include_once $filepath;
280                     }
281 
282                     $this->controller = $this->defaultController;
283                     $this->method     = self::$routeDefaults['method'];
284                     $this->methodArgs = self::$routeDefaults['args'];
285                     $this->dir        = self::$routeDefaults['dir'];
286                 } else {
287                     $resolved = false;
288 
289                     //Check if there's a route
290                     foreach ($this->routes as $source => $dest) {
291                         $sourceReplace =
292                             trim(str_replace(self::PREG_DELIMITER, '\\' . self::PREG_DELIMITER, $source), '/');
293                         $regex         =
294                             self::PREG_DELIMITER . '^' . $sourceReplace . '/?' . '$' . self::PREG_DELIMITER . 'is';
295 
296                         if (preg_match($regex, $this->path)) {
297                             $resolved = true;
298                             $explode  = explode('/', $this->path);
299 
300                             $this->dir        =
301                                 $dest['dir'] ? $dest['dir'] . DIRECTORY_SEPARATOR : self::$routeDefaults['dir'];
302                             $this->controller = isset($dest['class']) ? $dest['class'] : $explode[0];
303 
304                             //Remove controller
305                             array_shift($explode);
306 
307                             //Set method
308                             if (strpos($dest['method'], '$') !== false) {
309                                 $this->method = preg_replace($regex, $dest['method'], $this->path);
310                             } elseif ($dest['method'] != self::$routeDefaults['method']) {
311                                 $this->method = $dest['method'];
312                             } elseif (isset($explode[0])) {
313                                 $this->method = $explode[0];
314                             } else {
315                                 $this->method = self::$routeDefaults['method'];
316                             }
317 
318                             //Remove controller method
319                             if (!empty($explode)) {
320                                 array_shift($explode);
321                             }
322 
323                             //Set preliminary method args
324                             if ($dest['args'] != self::$routeDefaults['args']) {
325                                 $this->methodArgs = $dest['args'];
326                             } elseif (!empty($explode)) {
327                                 $this->methodArgs = $explode;
328                             } else {
329                                 $this->methodArgs = self::$routeDefaults['args'];
330                             }
331 
332                             $replace = explode('/', preg_replace($regex, implode('/', $this->methodArgs), $this->path));
333 
334                             //Remove empties
335                             foreach ($replace as $k => $v) {
336                                 if ($v == '') {
337                                     unset($replace[$k]);
338                                 }
339                             }
340 
341                             $this->methodArgs = $replace;
342 
343                             break;
344                         }
345                     }
346 
347                     if (!$resolved) {
348                         //If not, assume the path is controller/method/arg1...
349                         $path = explode('/', $this->path);
350 
351                         $this->dir        = null;
352                         $this->controller = array_shift($path);
353                         $this->method     = empty($path) ? self::$routeDefaults['method'] : array_shift($path);
354                         $this->methodArgs = $path;
355                     }
356 
357                     $filepath =
358                         DIR_CONTROLLERS .
359                         str_replace('/', DIRECTORY_SEPARATOR, $this->dir) .
360                         $this->controller .
361                         '.php';
362 
363                     if (file_exists($filepath)) {
364                         include_once $filepath;
365                     }
366                 }
367 
368                 return $this;
369             }
370 
371             /**
372              * Initialises the routing variables
373              *
374              * @author Art <a.molcanovas@gmail.com>
375              * @throws CE When the config file is not found
376              * @throws CE When $error_controller_class is not present in the config file
377              * @throws CE When The default controller is not present in the config file
378              * @throws CE When $routes is not a valid array
379              * @throws CE When a route value is not an array.
380              * @return Router
381              */
382             protected function initRoutes() {
383                 $path = \Alo::loadConfig('router', true);
384 
385                 if (!file_exists($path)) {
386                     throw new CE('Routing config file not found.', CE::E_CONFIG_NOT_FOUND);
387                 } else {
388                     require $path;
389 
390                     if (!isset($errorControllerClass)) {
391                         throw new CE('Error controller class not found in config file.', CE::E_ERR_NOT_FOUND);
392                     } elseif (!isset($defaultController)) {
393                         throw new CE('$default_controller undefined in config file.', CE::E_DEFAULT_UNDEFINED);
394                     } elseif (!is_array(get($routes))) {
395                         throw new CE('The routes variable must be an associative array', CE::E_MALFORMED_ROUTES);
396                     } else {
397                         $this->errController     = $errorControllerClass;
398                         $this->defaultController = $defaultController;
399 
400                         foreach ($routes as $k => $v) {
401                             if (is_array($v)) {
402                                 $this->routes[strtolower($k)] = array_merge(self::$routeDefaults, $v);
403                             } else {
404                                 throw new CE('Route ' . $k . ' is not a valid array.', CE::E_MALFORMED_ROUTES);
405                             }
406                         }
407 
408                         \Log::debug('Routes initialised');
409                     }
410                 }
411 
412                 return $this;
413             }
414 
415             /**
416              * Initialises the raw path variable
417              *
418              * @author Art <a.molcanovas@gmail.com>
419              * @return Router
420              */
421             protected function initPath() {
422                 if (isset($_SERVER['PATH_INFO'])) {
423                     $this->path = ltrim($_SERVER['PATH_INFO'], '/');
424                 } elseif (isset($_SERVER['argv'])) {
425                     //Shift off the "index.php" bit
426                     array_shift($_SERVER['argv']);
427                     $this->path = join(DIRECTORY_SEPARATOR, $_SERVER['argv']);
428                 } else {
429                     $this->path = '';
430                 }
431 
432                 $this->path = strtolower($this->path);
433 
434                 return $this;
435             }
436 
437             /**
438              * Initialises most server variables
439              *
440              * @author Art <a.molcanovas@gmail.com>
441              * @return Router
442              */
443             protected function initServerVars() {
444                 $this->port          = \get($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : null;
445                 $this->remoteAddr    = \get($_SERVER['REMOTE_ADDR']);
446                 $this->requestScheme = \get($_SERVER['REQUEST_SCHEME']);
447                 $this->requestMethod = \get($_SERVER['REQUEST_METHOD']);
448                 $this->serverAddr    = \get($_SERVER['SERVER_ADDR']);
449                 $this->serverName    = \get($_SERVER['SERVER_NAME']);
450 
451                 return $this;
452             }
453 
454             /**
455              * Returns whether this is a CLI request
456              *
457              * @author Art <a.molcanovas@gmail.com>
458              * @return bool
459              */
460             function isCliRequest() {
461                 return $this->isCliRequest;
462             }
463 
464             /**
465              * Returns whether this is an AJAX request
466              *
467              * @author Art <a.molcanovas@gmail.com>
468              * @return bool
469              */
470             function isAjaxRequest() {
471                 return $this->isAjaxRequest;
472             }
473 
474             /**
475              * Returns the error controller name
476              *
477              * @author Art <a.molcanovas@gmail.com>
478              * @return string
479              */
480             function getErrController() {
481                 return $this->errController;
482             }
483 
484             /**
485              * Returns the controller method name
486              *
487              * @author Art <a.molcanovas@gmail.com>
488              * @return string
489              */
490             function getMethod() {
491                 return $this->method;
492             }
493 
494             /**
495              * Returns the controller name
496              *
497              * @author Art <a.molcanovas@gmail.com>
498              * @return string
499              */
500             function getController() {
501                 return $this->controller;
502             }
503 
504             /**
505              * Returns the request port used
506              *
507              * @author Art <a.molcanovas@gmail.com>
508              * @return int
509              */
510             function getPort() {
511                 return $this->port;
512             }
513 
514             /**
515              * Returns the directory name
516              *
517              * @author Art <a.molcanovas@gmail.com>
518              * @return string
519              */
520             function getDir() {
521                 return $this->dir;
522             }
523 
524             /**
525              * Returns the request remote IP
526              *
527              * @author Art <a.molcanovas@gmail.com>
528              * @return string
529              */
530             function getRemoteAddr() {
531                 return $this->remoteAddr;
532             }
533 
534             /**
535              * Returns the request method used
536              *
537              * @author Art <a.molcanovas@gmail.com>
538              * @return string
539              */
540             function getRequestMethod() {
541                 return $this->requestMethod;
542             }
543 
544             /**
545              * Returns the request scheme used
546              *
547              * @author Art <a.molcanovas@gmail.com>
548              * @return string
549              */
550             function getRequestScheme() {
551                 return $this->requestScheme;
552             }
553 
554             /**
555              * Returns the server internal IP
556              *
557              * @author Art <a.molcanovas@gmail.com>
558              * @return string
559              */
560             function getServerAddr() {
561                 return $this->serverAddr;
562             }
563 
564             /**
565              * Returns the server name
566              *
567              * @author Art <a.molcanovas@gmail.com>
568              * @return string
569              */
570             function getServerName() {
571                 return $this->serverName;
572             }
573 
574             /**
575              * Returns the request path
576              *
577              * @author Art <a.molcanovas@gmail.com>
578              * @return string
579              */
580             function getPath() {
581                 return $this->path;
582             }
583 
584             /**
585              * Returns a string representation of the object data
586              *
587              * @author Art <a.molcanovas@gmail.com>
588              * @return string
589              */
590             function __toString() {
591                 return strip_tags(\debugLite($this));
592             }
593 
594         }
595     }
596 
AloFramework documentation API documentation generated byApiGen 2.8.0