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 ($dest['method'] != self::$routeDefaults['method']) {
309                                 $this->method = $dest['method'];
310                             } elseif (isset($explode[0])) {
311                                 $this->method = $explode[0];
312                             } else {
313                                 $this->method = self::$routeDefaults['method'];
314                             }
315 
316                             //Remove controller method
317                             if (!empty($explode)) {
318                                 array_shift($explode);
319                             }
320 
321                             //Set preliminary method args
322                             if ($dest['args'] != self::$routeDefaults['args']) {
323                                 $this->methodArgs = $dest['args'];
324                             } elseif (!empty($explode)) {
325                                 $this->methodArgs = $explode;
326                             } else {
327                                 $this->methodArgs = self::$routeDefaults['args'];
328                             }
329 
330                             $replace = explode('/', preg_replace($regex, implode('/', $this->methodArgs), $this->path));
331 
332                             //Remove empties
333                             foreach ($replace as $k => $v) {
334                                 if ($v == '') {
335                                     unset($replace[$k]);
336                                 }
337                             }
338 
339                             $this->methodArgs = $replace;
340 
341                             break;
342                         }
343                     }
344 
345                     if (!$resolved) {
346                         //If not, assume the path is controller/method/arg1...
347                         $path = explode('/', $this->path);
348 
349                         $this->dir        = null;
350                         $this->controller = array_shift($path);
351                         $this->method     = empty($path) ? self::$routeDefaults['method'] : array_shift($path);
352                         $this->methodArgs = $path;
353                     }
354 
355                     $filepath =
356                         DIR_CONTROLLERS .
357                         str_replace('/', DIRECTORY_SEPARATOR, $this->dir) .
358                         $this->controller .
359                         '.php';
360 
361                     if (file_exists($filepath)) {
362                         include_once $filepath;
363                     }
364                 }
365 
366                 return $this;
367             }
368 
369             /**
370              * Initialises the routing variables
371              *
372              * @author Art <a.molcanovas@gmail.com>
373              * @throws CE When the config file is not found
374              * @throws CE When $error_controller_class is not present in the config file
375              * @throws CE When The default controller is not present in the config file
376              * @throws CE When $routes is not a valid array
377              * @throws CE When a route value is not an array.
378              * @return Router
379              */
380             protected function initRoutes() {
381                 $path = \Alo::loadConfig('router', true);
382 
383                 if (!file_exists($path)) {
384                     throw new CE('Routing config file not found.', CE::E_CONFIG_NOT_FOUND);
385                 } else {
386                     require $path;
387 
388                     if (!isset($errorControllerClass)) {
389                         throw new CE('Error controller class not found in config file.', CE::E_ERR_NOT_FOUND);
390                     } elseif (!isset($defaultController)) {
391                         throw new CE('$default_controller undefined in config file.', CE::E_DEFAULT_UNDEFINED);
392                     } elseif (!is_array(get($routes))) {
393                         throw new CE('The routes variable must be an associative array', CE::E_MALFORMED_ROUTES);
394                     } else {
395                         $this->errController     = $errorControllerClass;
396                         $this->defaultController = $defaultController;
397 
398                         foreach ($routes as $k => $v) {
399                             if (is_array($v)) {
400                                 $this->routes[strtolower($k)] = array_merge(self::$routeDefaults, $v);
401                             } else {
402                                 throw new CE('Route ' . $k . ' is not a valid array.', CE::E_MALFORMED_ROUTES);
403                             }
404                         }
405 
406                         \Log::debug('Routes initialised');
407                     }
408                 }
409 
410                 return $this;
411             }
412 
413             /**
414              * Initialises the raw path variable
415              *
416              * @author Art <a.molcanovas@gmail.com>
417              * @return Router
418              */
419             protected function initPath() {
420                 if (isset($_SERVER['PATH_INFO'])) {
421                     $this->path = ltrim($_SERVER['PATH_INFO'], '/');
422                 } elseif (isset($_SERVER['argv'])) {
423                     //Shift off the "index.php" bit
424                     array_shift($_SERVER['argv']);
425                     $this->path = join(DIRECTORY_SEPARATOR, $_SERVER['argv']);
426                 } else {
427                     $this->path = '';
428                 }
429 
430                 $this->path = strtolower($this->path);
431 
432                 return $this;
433             }
434 
435             /**
436              * Initialises most server variables
437              *
438              * @author Art <a.molcanovas@gmail.com>
439              * @return Router
440              */
441             protected function initServerVars() {
442                 $this->port          = \get($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : null;
443                 $this->remoteAddr    = \get($_SERVER['REMOTE_ADDR']);
444                 $this->requestScheme = \get($_SERVER['REQUEST_SCHEME']);
445                 $this->requestMethod = \get($_SERVER['REQUEST_METHOD']);
446                 $this->serverAddr    = \get($_SERVER['SERVER_ADDR']);
447                 $this->serverName    = \get($_SERVER['SERVER_NAME']);
448 
449                 return $this;
450             }
451 
452             /**
453              * Returns whether this is a CLI request
454              *
455              * @author Art <a.molcanovas@gmail.com>
456              * @return bool
457              */
458             function isCliRequest() {
459                 return $this->isCliRequest;
460             }
461 
462             /**
463              * Returns whether this is an AJAX request
464              *
465              * @author Art <a.molcanovas@gmail.com>
466              * @return bool
467              */
468             function isAjaxRequest() {
469                 return $this->isAjaxRequest;
470             }
471 
472             /**
473              * Returns the error controller name
474              *
475              * @author Art <a.molcanovas@gmail.com>
476              * @return string
477              */
478             function getErrController() {
479                 return $this->errController;
480             }
481 
482             /**
483              * Returns the controller method name
484              *
485              * @author Art <a.molcanovas@gmail.com>
486              * @return string
487              */
488             function getMethod() {
489                 return $this->method;
490             }
491 
492             /**
493              * Returns the controller name
494              *
495              * @author Art <a.molcanovas@gmail.com>
496              * @return string
497              */
498             function getController() {
499                 return $this->controller;
500             }
501 
502             /**
503              * Returns the request port used
504              *
505              * @author Art <a.molcanovas@gmail.com>
506              * @return int
507              */
508             function getPort() {
509                 return $this->port;
510             }
511 
512             /**
513              * Returns the directory name
514              *
515              * @author Art <a.molcanovas@gmail.com>
516              * @return string
517              */
518             function getDir() {
519                 return $this->dir;
520             }
521 
522             /**
523              * Returns the request remote IP
524              *
525              * @author Art <a.molcanovas@gmail.com>
526              * @return string
527              */
528             function getRemoteAddr() {
529                 return $this->remoteAddr;
530             }
531 
532             /**
533              * Returns the request method used
534              *
535              * @author Art <a.molcanovas@gmail.com>
536              * @return string
537              */
538             function getRequestMethod() {
539                 return $this->requestMethod;
540             }
541 
542             /**
543              * Returns the request scheme used
544              *
545              * @author Art <a.molcanovas@gmail.com>
546              * @return string
547              */
548             function getRequestScheme() {
549                 return $this->requestScheme;
550             }
551 
552             /**
553              * Returns the server internal IP
554              *
555              * @author Art <a.molcanovas@gmail.com>
556              * @return string
557              */
558             function getServerAddr() {
559                 return $this->serverAddr;
560             }
561 
562             /**
563              * Returns the server name
564              *
565              * @author Art <a.molcanovas@gmail.com>
566              * @return string
567              */
568             function getServerName() {
569                 return $this->serverName;
570             }
571 
572             /**
573              * Returns the request path
574              *
575              * @author Art <a.molcanovas@gmail.com>
576              * @return string
577              */
578             function getPath() {
579                 return $this->path;
580             }
581 
582             /**
583              * Returns a string representation of the object data
584              *
585              * @author Art <a.molcanovas@gmail.com>
586              * @return string
587              */
588             function __toString() {
589                 return strip_tags(\debugLite($this));
590             }
591 
592         }
593     }
594 
AloFramework documentation API documentation generated byApiGen 2.8.0