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

Namespaces

  • Alo
    • Cache
    • CLI
    • Controller
    • Db
    • 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                     ) {
174                         $rm = $rc->getMethod($this->method);
175 
176                         //And a public method
177                         if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
178                             //Excellent. Instantiate!
179                             $init = true;
180                         } else {
181                             $this->forceError();
182                         }
183                     } else {
184                         $this->forceError();
185                     }
186                 } catch (\ReflectionException $ex) {
187                     $this->forceError($ex->getMessage());
188                 }
189 
190                 if ($init) {
191                     \Log::debug('Initialising controller ' . $this->controller . '->' . $this->method . '(' .
192                                 implode(',', $this->methodArgs) . ')');
193                     $controllerName  = self::CONTROLLER_NAMESPACE . $this->controller;
194                     Alo::$controller = new $controllerName;
195                     call_user_func_array([Alo::$controller, $this->method], $this->methodArgs);
196                 }
197 
198                 return $this;
199             }
200 
201             /**
202              * Forces the error controller
203              *
204              * @author Art <a.molcanovas@gmail.com>
205              *
206              * @param string $msg Optionally, the error message thrown by ReflectionClass
207              *                    or ReflectionMethod
208              *
209              * @return \Alo\Controller\Router
210              * @throws CE If the controller is already the error controller
211              * @uses   self::tryCall()
212              */
213             protected function forceError($msg = null) {
214                 if ($this->controller != $this->errController) {
215                     \Log::debug('404\'d on path: ' . $this->path . '. Settings were as follows: dir: ' . $this->dir .
216                                 ', class: ' . $this->controller . ', method: ' . $this->method . ', args: ' .
217                                 json_encode($this->methodArgs));
218 
219                     $path = DIR_CONTROLLERS . strtolower($this->errController) . '.php';
220                     if (file_exists($path)) {
221                         include_once $path;
222                     }
223 
224                     $this->controller = $this->errController;
225                     $this->method     = 'error';
226                     $this->methodArgs = [404];
227                     $this->tryCall();
228                 } else {
229                     throw new CE('No route available and the error controller ' . 'is invalid.' .
230                                  ($msg ? ' Exception message returned: ' . $msg : ''), CE::E_INVALID_ROUTE);
231                 }
232 
233                 return $this;
234             }
235 
236             /**
237              * Same as init(), but without attempting to call the controller
238              *
239              * @author Art <a.molcanovas@gmail.com>
240              *
241              * @throws CE When the config file is not found
242              * @throws CE When $error_controller_class is not present in the config file
243              * @throws CE When The default controller is not present in the config file
244              * @throws CE When $routes is not a valid array
245              * @throws CE When a route value is not an array.
246              * @return Router
247              */
248             function initNoCall() {
249                 $this->isCliRequest  = php_sapi_name() == 'cli' || defined('STDIN');
250                 $this->isAjaxRequest = \get($_SERVER['HTTP_X_REQUESTED_WITH']) == 'XMLHttpRequest';
251 
252                 return $this->initServerVars()->initPath()->initRoutes()->resolvePath();
253             }
254 
255             /**
256              * Resolves the controller/method path
257              *
258              * @author Art <a.molcanovas@gmail.com>
259              * @return Router
260              */
261             protected function resolvePath() {
262                 //Use the default controller if the path is unavailable
263                 if (!$this->path) {
264                     $filepath = DIR_CONTROLLERS . strtolower($this->defaultController) . '.php';
265 
266                     if (file_exists($filepath)) {
267                         include_once $filepath;
268                     }
269 
270                     $this->controller = $this->defaultController;
271                     $this->method     = self::$routeDefaults['method'];
272                     $this->methodArgs = self::$routeDefaults['args'];
273                     $this->dir        = self::$routeDefaults['dir'];
274                 } else {
275                     $resolved = false;
276 
277                     //Check if there's a route
278                     foreach ($this->routes as $source => $dest) {
279                         $sourceReplace =
280                             trim(str_replace(self::PREG_DELIMITER, '\\' . self::PREG_DELIMITER, $source), '/');
281                         $regex         =
282                             self::PREG_DELIMITER . '^' . $sourceReplace . '/?' . '$' . self::PREG_DELIMITER . 'is';
283 
284                         if (preg_match($regex, $this->path)) {
285                             $resolved = true;
286                             $explode  = explode('/', $this->path);
287 
288                             $this->dir        =
289                                 $dest['dir'] ? $dest['dir'] . DIRECTORY_SEPARATOR : self::$routeDefaults['dir'];
290                             $this->controller = isset($dest['class']) ? $dest['class'] : $explode[0];
291 
292                             //Remove controller
293                             array_shift($explode);
294 
295                             //Set method
296                             if ($dest['method'] != self::$routeDefaults['method']) {
297                                 $this->method = $dest['method'];
298                             } elseif (isset($explode[0])) {
299                                 $this->method = $explode[0];
300                             } else {
301                                 $this->method = self::$routeDefaults['method'];
302                             }
303 
304                             //Remove controller method
305                             if (!empty($explode)) {
306                                 array_shift($explode);
307                             }
308 
309                             //Set preliminary method args
310                             if ($dest['args'] != self::$routeDefaults['args']) {
311                                 $this->methodArgs = $dest['args'];
312                             } elseif (!empty($explode)) {
313                                 $this->methodArgs = $explode;
314                             } else {
315                                 $this->methodArgs = self::$routeDefaults['args'];
316                             }
317 
318                             $replace = explode('/', preg_replace($regex, implode('/', $this->methodArgs), $this->path));
319 
320                             //Remove empties
321                             foreach ($replace as $k => $v) {
322                                 if ($v == '') {
323                                     unset($replace[$k]);
324                                 }
325                             }
326 
327                             $this->methodArgs = $replace;
328 
329                             break;
330                         }
331                     }
332 
333                     if (!$resolved) {
334                         //If not, assume the path is controller/method/arg1...
335                         $path = explode('/', $this->path);
336 
337                         $this->dir        = null;
338                         $this->controller = array_shift($path);
339                         $this->method     = empty($path) ? self::$routeDefaults['method'] : array_shift($path);
340                         $this->methodArgs = $path;
341                     }
342 
343                     $filepath =
344                         DIR_CONTROLLERS . str_replace('/', DIRECTORY_SEPARATOR, $this->dir) . $this->controller .
345                         '.php';
346 
347                     if (file_exists($filepath)) {
348                         include_once $filepath;
349                     }
350                 }
351 
352                 return $this;
353             }
354 
355             /**
356              * Initialises the routing variables
357              *
358              * @author Art <a.molcanovas@gmail.com>
359              * @throws CE When the config file is not found
360              * @throws CE When $error_controller_class is not present in the config file
361              * @throws CE When The default controller is not present in the config file
362              * @throws CE When $routes is not a valid array
363              * @throws CE When a route value is not an array.
364              * @return Router
365              */
366             protected function initRoutes() {
367                 $path = \Alo::loadConfig('router', true);
368 
369                 if (!file_exists($path)) {
370                     throw new CE('Routing config file not found.', CE::E_CONFIG_NOT_FOUND);
371                 } else {
372                     require $path;
373 
374                     if (!isset($errorControllerClass)) {
375                         throw new CE('Error controller class not found in config file.', CE::E_ERR_NOT_FOUND);
376                     } elseif (!isset($defaultController)) {
377                         throw new CE('$default_controller undefined in config file.', CE::E_DEFAULT_UNDEFINED);
378                     } elseif (!is_array(get($routes))) {
379                         throw new CE('The routes variable must be an associative array', CE::E_MALFORMED_ROUTES);
380                     } else {
381                         $this->errController     = $errorControllerClass;
382                         $this->defaultController = $defaultController;
383 
384                         foreach ($routes as $k => $v) {
385                             if (is_array($v)) {
386                                 $this->routes[strtolower($k)] = array_merge(self::$routeDefaults, $v);
387                             } else {
388                                 throw new CE('Route ' . $k . ' is not a valid array.', CE::E_MALFORMED_ROUTES);
389                             }
390                         }
391 
392                         \Log::debug('Routes initialised');
393                     }
394                 }
395 
396                 return $this;
397             }
398 
399             /**
400              * Initialises the raw path variable
401              *
402              * @author Art <a.molcanovas@gmail.com>
403              * @return Router
404              */
405             protected function initPath() {
406                 if (isset($_SERVER['PATH_INFO'])) {
407                     $this->path = ltrim($_SERVER['PATH_INFO'], '/');
408                 } elseif (isset($_SERVER['argv'])) {
409                     //Shift off the "index.php" bit
410                     array_shift($_SERVER['argv']);
411                     $this->path = join(DIRECTORY_SEPARATOR, $_SERVER['argv']);
412                 } else {
413                     $this->path = '';
414                 }
415 
416                 $this->path = strtolower($this->path);
417 
418                 return $this;
419             }
420 
421             /**
422              * Initialises most server variables
423              *
424              * @author Art <a.molcanovas@gmail.com>
425              * @return Router
426              */
427             protected function initServerVars() {
428                 $this->port          = \get($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : null;
429                 $this->remoteAddr    = \get($_SERVER['REMOTE_ADDR']);
430                 $this->requestScheme = \get($_SERVER['REQUEST_SCHEME']);
431                 $this->requestMethod = \get($_SERVER['REQUEST_METHOD']);
432                 $this->serverAddr    = \get($_SERVER['SERVER_ADDR']);
433                 $this->serverName    = \get($_SERVER['SERVER_NAME']);
434 
435                 return $this;
436             }
437 
438             /**
439              * Returns whether this is a CLI request
440              *
441              * @author Art <a.molcanovas@gmail.com>
442              * @return bool
443              */
444             function isCliRequest() {
445                 return $this->isCliRequest;
446             }
447 
448             /**
449              * Returns whether this is an AJAX request
450              *
451              * @author Art <a.molcanovas@gmail.com>
452              * @return bool
453              */
454             function isAjaxRequest() {
455                 return $this->isAjaxRequest;
456             }
457 
458             /**
459              * Returns the error controller name
460              *
461              * @author Art <a.molcanovas@gmail.com>
462              * @return string
463              */
464             function getErrController() {
465                 return $this->errController;
466             }
467 
468             /**
469              * Returns the controller method name
470              *
471              * @author Art <a.molcanovas@gmail.com>
472              * @return string
473              */
474             function getMethod() {
475                 return $this->method;
476             }
477 
478             /**
479              * Returns the controller name
480              *
481              * @author Art <a.molcanovas@gmail.com>
482              * @return string
483              */
484             function getController() {
485                 return $this->controller;
486             }
487 
488             /**
489              * Returns the request port used
490              *
491              * @author Art <a.molcanovas@gmail.com>
492              * @return int
493              */
494             function getPort() {
495                 return $this->port;
496             }
497 
498             /**
499              * Returns the directory name
500              *
501              * @author Art <a.molcanovas@gmail.com>
502              * @return string
503              */
504             function getDir() {
505                 return $this->dir;
506             }
507 
508             /**
509              * Returns the request remote IP
510              *
511              * @author Art <a.molcanovas@gmail.com>
512              * @return string
513              */
514             function getRemoteAddr() {
515                 return $this->remoteAddr;
516             }
517 
518             /**
519              * Returns the request method used
520              *
521              * @author Art <a.molcanovas@gmail.com>
522              * @return string
523              */
524             function getRequestMethod() {
525                 return $this->requestMethod;
526             }
527 
528             /**
529              * Returns the request scheme used
530              *
531              * @author Art <a.molcanovas@gmail.com>
532              * @return string
533              */
534             function getRequestScheme() {
535                 return $this->requestScheme;
536             }
537 
538             /**
539              * Returns the server internal IP
540              *
541              * @author Art <a.molcanovas@gmail.com>
542              * @return string
543              */
544             function getServerAddr() {
545                 return $this->serverAddr;
546             }
547 
548             /**
549              * Returns the server name
550              *
551              * @author Art <a.molcanovas@gmail.com>
552              * @return string
553              */
554             function getServerName() {
555                 return $this->serverName;
556             }
557 
558             /**
559              * Returns the request path
560              *
561              * @author Art <a.molcanovas@gmail.com>
562              * @return string
563              */
564             function getPath() {
565                 return $this->path;
566             }
567 
568             /**
569              * Returns a string representation of the object data
570              *
571              * @author Art <a.molcanovas@gmail.com>
572              * @return string
573              */
574             function __toString() {
575                 return strip_tags(\debugLite($this));
576             }
577 
578         }
579     }
580 
AloFramework documentation API documentation generated byApiGen 2.8.0