Overview

Namespaces

  • Sleepy
  • Module
    • Authentication
    • CSV
    • DB
    • FormBuilder
    • FSDB
    • IP2Country
    • Mailer
    • MobiDetect
    • Navigation
    • StaticCache
  • PHP

Classes

  • Sleepy\Debug
  • Sleepy\Hook
  • Sleepy\Router
  • Sleepy\SM
  • Sleepy\Template

Exceptions

  • Sleepy\RouteNotFound
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: namespace Sleepy;
  3: 
  4: /**
  5:  * Implements routing functionality based on a URI.
  6:  *
  7:  * ## Usage
  8:  *
  9:  * <code>
 10:  *   \Sleepy\Router::route('/user/{{ id }}/*', function ($route) {
 11:  *       echo "The route uses the pattern: ", $route->pattern;
 12:  *       echo "Route was matched using method: ", $route->method;
 13:  *       echo "The wildcard matched: ", $route->splat;
 14:  *       echo "Showing user ", $route->params['id'], "</br>";
 15:  *   });
 16:  * </code>
 17:  *
 18:  * ## Changelog
 19:  *
 20:  * ### Version 1.1
 21:  * * updated private prefix (_) for consistency
 22:  * * updated documentation
 23:  *
 24:  * ### Version 1.0
 25:  * * With unit tests in place, we're ready to call this 1.0
 26:  *
 27:  * ### Version 0.4
 28:  * * Simplified interface, thanks @cameff
 29:  *
 30:  * @todo  Document the class and add it to homepage
 31:  *
 32:  * @date July 18, 2016
 33:  * @author Jaime A. Rodriguez <hi.i.am.jaime@gmail.com>
 34:  * @version 1.1
 35:  * @license  http://opensource.org/licenses/MIT
 36:  */
 37: class Router {
 38:     /**
 39:      * An array of routes
 40:      *
 41:      * @var array
 42:      * @private
 43:      */
 44:     private static $_routes = array();
 45: 
 46:     /**
 47:      * Has a route been matched?
 48:      *
 49:      * @var boolean True, if we matched a route
 50:      */
 51:     public static $routeFound = false;
 52: 
 53:     /**
 54:      * The delimiter for the route pattern
 55:      *
 56:      * @var string
 57:      */
 58:     public static $delimiter = '/';
 59: 
 60:     /**
 61:      * If true, parse the querystring instead of the path
 62:      *
 63:      * @var boolean
 64:      */
 65:     public static $querystring = false;
 66: 
 67:     /**
 68:      * An array of parameters, either from the path or querystring
 69:      *
 70:      * @var array
 71:      */
 72:     public static $parameters = array();
 73: 
 74:     /**
 75:      * Gets an array from a string based on Router::$delimeter
 76:      *
 77:      * @param  string $string a string to explode()
 78:      * @return array          an exploded string
 79:      */
 80:     public static function getArray($string) {
 81:         if (substr($string, strlen($string) - 1, 1) == self::$delimiter) {
 82:             $string = substr($string, 0, strlen($string) - 1);
 83:         }
 84: 
 85:         if (substr($string, 0, 1) != self::$delimiter) {
 86:             $string = self::$delimiter . $string;
 87:         }
 88: 
 89:         return explode(self::$delimiter, $string);;
 90:     }
 91: 
 92:     /**
 93:      * Creates a new route
 94:      *
 95:      * @param  string   $pattern A pattern to match
 96:      * @param  function $func    A callback function
 97:      * @return object            \Sleepy\Route()
 98:      */
 99:     public static function route($pattern, $func) {
100:         if (is_array($pattern)) {
101:             $route = new _Route(md5($pattern[0]));
102:         } else {
103:             $route = new _Route(md5($pattern));
104:         }
105: 
106:         array_push(self::$_routes, $route);
107:         $route->add($pattern, $func);
108:         return $route;
109:     }
110: 
111:     /**
112:      * Creates a new route, uses a controller and view
113:      *
114:      * @param  string   $pattern A pattern to match
115:      * @param  function $func    A callback function
116:      * @return object            \Sleepy\Route()
117:      */
118:     public static function mvc($pattern, $defaults = array()) {
119:         self::route($pattern, function ($route) use ($defaults) {
120:             // set default for defaults... (-_-)
121:             $defaults['controller'] = (array_key_exists('controller', $defaults)) ? $defaults['controller'] : 'home';
122:             $defaults['action'] = (array_key_exists('action', $defaults)) ? $defaults['action'] : 'index';
123:             $defaults['id'] = (array_key_exists('id', $defaults)) ? $defaults['id'] : '';
124: 
125:             if (!is_array($route->params)) $route->params = array();
126: 
127:             // Set default controller, action, and id
128:             $controller = (array_key_exists('controller', $route->params)) ? $route->params['controller'] : $defaults['controller'];
129:             $action = (array_key_exists('action', $route->params)) ? $route->params['action'] : $defaults['action'];
130:             $id = (array_key_exists('id', $route->params)) ? $route->params['id'] : $defaults['id'];
131: 
132:             // Make all the defaults available in the routes parameters
133:             $route->params = array_merge($defaults, $route->params);
134: 
135:             $controller_file = $_SERVER['DOCUMENT_ROOT'] . '/app/controllers/';
136:             $controller_file .= strtolower($controller) . '.php';
137: 
138:             //Sterilize
139:             $controller = strtolower($controller);
140:             $controller = str_replace('-', '', $controller);
141:             $action = str_replace('-', '_', $action);
142: 
143:             // Call Controller::action($route)
144:             if (file_exists($controller_file)) {
145:                 require_once($controller_file);
146:                 if (class_exists($controller)) {
147:                     $c = new $controller;
148:                     if (method_exists($c, $action)) {
149:                         $c->$action($route);
150:                     } else {
151:                         throw new RouteNotFound("Router: Action ($action) does not exist in Controller ($controller).");
152:                     }
153:                 } else {
154:                     throw new RouteNotFound("Router: Controller ($controller) does not exist.");
155:                 }
156:             } else {
157:                 throw new RouteNotFound("Router: Controller File ($controller_file) does not exist.");
158:             }
159:         });
160:     }
161: 
162:     /**
163:      * Creates a new route
164:      *
165:      * @param  string   $pattern A pattern to match
166:      * @param  function $func    A callback function
167:      * @return object            \Sleepy\Route()
168:      */
169:     public static function redirect($controller, $action='index', $params='') {
170:         $route = new _Route(md5("{{ $controller }}/{{ $action }}/{{ id }}/*"));
171:         $route->params = $params;
172: 
173:         $controller_file = $_SERVER['DOCUMENT_ROOT'] . '/app/controllers/';
174:         $controller_file .= strtolower($controller) . '.php';
175: 
176:         //Sterilize
177:         $controller = strtolower($controller);
178:         $controller = str_replace('-', '', $controller);
179:         $action = str_replace('-', '_', $action);
180: 
181:         // Call Controller::action($route)
182:         if (file_exists($controller_file)) {
183:             require_once($controller_file);
184:             if (class_exists($controller)) {
185:                 $c = new $controller;
186:                 if (method_exists($c, $action)) {
187:                     $c->$action($route);
188:                 } else {
189:                     throw new \Exception("Router: Action ($action) does not exist.");
190:                 }
191:             } else {
192:                 throw new \Exception("Router: Controller ($controller) does not exist.");
193:             }
194:         } else {
195:             throw new \Exception("Router: Controller File ($controller_file) does not exist.");
196:         }
197:     }
198: 
199:     /**
200:      * Starts parsing the Router::routes
201:      *
202:      * @return boolean true if a route was matched
203:      */
204:     public static function start($currentPath='') {
205:         self::$routeFound = false;
206: 
207:         if ($currentPath == '') {
208:             $currentPath = $_SERVER['REQUEST_URI'];
209:         }
210: 
211:         if (self::$querystring) {
212:             $currentPath = str_replace('/?q=', '', $currentPath);
213:         }
214: 
215:         // Get all parameters
216:         self::$parameters = self::getArray($currentPath);
217: 
218:         foreach (self::$_routes as $route) {
219:             $route->method = $_SERVER['REQUEST_METHOD'];
220:             $route->execute();
221:         }
222: 
223:         if (!self::$routeFound) {
224:             throw new RouteNotFound('Router: Route not found.');
225:         }
226: 
227:         return self::$routeFound;
228:     }
229: 
230:     public static function reset() {
231:         self::$_routes = array();
232:     }
233: }
234: 
235: /**
236:  * Private class used by the Router class
237:  *
238:  * ### Usage
239:  *
240:  * This class is private and should not be instatiated outside of the Router
241:  * class
242:  *
243:  * ### Changelog
244:  *
245:  * ## Version 0.4
246:  * * Bug fixes
247:  *
248:  * @todo  Write tests for the class
249:  *
250:  * @date September 31, 2014
251:  * @author Jaime A. Rodriguez <hi.i.am.jaime@gmail.com>
252:  * @version 0.4
253:  * @license  http://opensource.org/licenses/MIT
254:  * @internal
255:  */
256: class _Route {
257:     /**
258:      * A list of (pattern ,callbacks)
259:      *
260:      * @var array
261:      */
262:     private $_functions = array();
263: 
264:     /**
265:      * The name of the route, MD5 hash of pattern by default
266:      *
267:      * @var string
268:      */
269:     public $name;
270: 
271:     /**
272:      * A hash of matched placeholder
273:      *
274:      * @var string[]
275:      */
276:     public $params;
277: 
278:     /**
279:      * The method that was matched
280:      *
281:      * @var string
282:      */
283:     public $method;
284: 
285:     /**
286:      * Returns the string matched with a wildcard
287:      *
288:      * @var string
289:      */
290:     public $splat;
291: 
292:     /**
293:      * Cleans the handlebars from placeholders
294:      *
295:      * @param  string $placeholder The full placeholder
296:      * @return string              The stripped placeholder
297:      */
298:     private function _cleanPlaceholder($placeholder) {
299:         $key = str_replace('{{', '', $placeholder);
300:         $key = str_replace('}}', '', $key);
301:         return trim($key);
302:     }
303: 
304:     /**
305:      * Is this $string a placeholder
306:      *
307:      * @param  string  $string  The possible placeholder
308:      * @return boolean          True, if it is a placeholder
309:      */
310:     private function _isPlaceholder($string) {
311:         return substr($string, 0,2) == '{{' && substr($string, strlen($string) - 2, 2) == '}}';
312:     }
313: 
314:     /**
315:      * Run all the filters for a placeholder
316:      *
317:      * @param  string $key    The placeholder
318:      * @param  string $string The string to parse
319:      * @return string         The parsed string
320:      * @private
321:      */
322:     private function _runFilters($key, $string) {
323:         if (class_exists('\Sleepy\Hook')) {
324:             $string = \Sleepy\Hook::addFilter('route_parameters', $string);
325:             $string = \Sleepy\Hook::addFilter('route_parameter_' . $key, $string);
326:             $string = \Sleepy\Hook::addFilter('route_' . $this->name . '_parameters', $string);
327:             $string = \Sleepy\Hook::addFilter('route_' . $this->name . '_parameter_' . $key, $string);
328:         }
329: 
330:         return $string;
331:     }
332: 
333:     /**
334:      * Does the pattern have a wildcard?
335:      *
336:      * @return boolean True, if there is a wildcard
337:      * @private
338:      */
339:     private function _hasWildcard($pattern) {
340:         if (strlen($pattern) == 0) {
341:             return false;
342:         } else {
343:             return strpos($pattern, '*') !== false;
344:         }
345:     }
346: 
347:     /**
348:      * Store the variables found in the route
349: 
350:      * @param  string $key   The placeholder
351:      * @param  string $value The value
352:      * @return boolean       True, if we succeeded
353:      * @private
354:      */
355:     private function _storeVariable($key, $value) {
356:         if ($value == '') {
357:             return false;
358:         }
359: 
360:         $key = $this->_cleanPlaceholder($key);
361:         $value = $this->_runFilters($key, $value);
362: 
363:         // Check for multiple variables, they should match.
364:         if (isset($this->params[$key])) {
365:             if ($value != $this->params[$key]) {
366:                 return false;
367:             }
368:         }
369: 
370:         $this->params[$key] = $value;
371:         return true;
372:     }
373: 
374:     /**
375:      * Creates a new route
376:      *
377:      * @param string $name Optional.
378:      */
379:     public function __construct($name='') {
380:         $this->name = $name;
381:     }
382: 
383:     /**
384:      * if URL matches pattern do $func
385:      *
386:      * @param  string   $pattern a pattern with {{ placeholders }}
387:      * @param  function $func    Executes if pattern matches; func($variables)
388:      */
389:     public function add($pattern, $func) {
390:         // If we have an array of patterns match those individually
391:         if (is_array($pattern)) {
392:             foreach ($pattern as $p) {
393:                 $this->add($p, $func);
394:             }
395:         } else {
396:             array_push($this->_functions, array($pattern, $func));
397:         }
398: 
399:         return $this;
400:     }
401: 
402:     /**
403:      * Executes the call back functions
404:      */
405:     public function execute() {
406:         $noMatch = false;
407: 
408:         // Exit when there is nothing left to do
409:         if (count($this->_functions) < 1) {
410:             return;
411:         }
412: 
413:         // Shift a function off the queue
414:         $r = array_shift($this->_functions);
415:         $rawPattern = $r[0];
416:         $func = $r[1];
417: 
418:         if (Router::$routeFound) {
419:             $noMatch = true;
420:         } else {
421:             // Get array from string
422:             $pattern = Router::getArray($rawPattern);
423: 
424:             // If they are obviously different then stop the route
425:             if (count(Router::$parameters) == count($pattern) || $this->_hasWildcard($rawPattern)) {
426:                 // Check for matches, stop if we have a problem
427:                 foreach ($pattern as $idx => $value) {
428:                     // Store the variable
429:                     if ($this->_isPlaceholder($value)) {
430:                         if (!$this->_storeVariable($value, @Router::$parameters[$idx])) {
431:                             $noMatch = true;
432:                             break;
433:                         }
434: 
435:                         continue;
436:                     }
437: 
438:                     // If we are at a wildcard, we have a match!
439:                     if ($value == '*') {
440:                         $this->splat = implode(Router::$delimiter, array_slice(Router::$parameters, $idx));
441:                         break;
442:                     }
443: 
444:                     // If something doesn't match then stop the route.
445:                     if (!isset(Router::$parameters[$idx]) || $value != Router::$parameters[$idx]) {
446:                         $noMatch = true;
447:                         break;
448:                     }
449:                 }
450:             } else {
451:                 $noMatch = true;
452:             }
453:         }
454: 
455:         if ($noMatch) {
456:             if (class_exists('\Sleepy\Hook')) {
457:                 \Sleepy\Hook::addAction('route_failed');
458:                 \Sleepy\Hook::addAction('route_failed_' . $this->name);
459:             }
460:         } else {
461:             // Call route_start actions
462:             if (class_exists('\Sleepy\Hook')) {
463:                 \Sleepy\Hook::addAction('route_start');
464:                 \Sleepy\Hook::addAction('route_start_' . $this->name);
465:             }
466: 
467:             $this->pattern = $rawPattern;
468:             Router::$routeFound = true;
469:             $func($this);
470: 
471:             // Call route_end actions
472:             if (class_exists('\Sleepy\Hook')) {
473:                 \Sleepy\Hook::addAction('route_end');
474:                 \Sleepy\Hook::addAction('route_end_' . $this->name);
475:             }
476:         }
477: 
478:         // This wasn't it, let's try the next one
479:         $this->execute();
480:     }
481: }
482: 
483: /**
484:  * Exception: Route not Found
485:  */
486: class RouteNotFound extends \Exception {}
487: 
sleepyMUSTACHE v.0.8 API documentation generated by ApiGen