Added route passing functionality.

pull/42/merge
Mike Cao 12 years ago
parent 9fb6b54665
commit 28ae5e0aec

@ -124,8 +124,6 @@ Flight::route('GET|POST /', function(){
}); });
``` ```
Method specific routes have precedence over global routes.
## Regular Expressions ## Regular Expressions
You can use regular expressions in your routes: You can use regular expressions in your routes:
@ -189,6 +187,24 @@ Flight::route('*', function(){
}); });
``` ```
## Passing
You can pass execution on to the next matching route by returning `true` from your callback function.
```php
Flight::route('/user/@name', function($name){
// Check some condition
if ($name != "Bob") {
// Continue to next route
return true;
}
});
Flight::route('/user/*', function(){
// This will get called
});
```
# Extending # Extending
Flight is designed to be an extensible framework. The framework comes with a set of default methods and components, but it allows you to map your own methods, register your own classes, or even override existing classes and methods. Flight is designed to be an extensible framework. The framework comes with a set of default methods and components, but it allows you to map your own methods, register your own classes, or even override existing classes and methods.
@ -352,7 +368,7 @@ Flight::before('start', function(&$params, &$output){
Flight::before('start', function(&$params, &$output){ Flight::before('start', function(&$params, &$output){
echo 'two'; echo 'two';
// This will end the chain // This will end the chain
return false; return false;
}); });

@ -259,25 +259,31 @@ class Flight {
* Starts the framework. * Starts the framework.
*/ */
public static function _start() { public static function _start() {
$router = self::router(); $dispatched = false;
$request = self::request();
// Route the request // Route the request
$callback = $router->route($request); while ($route = self::router()->route(self::request())) {
$params = array_values($route->params);
if ($callback !== false) { $continue = self::$dispatcher->execute(
$params = array_values($router->params); $route->callback,
self::$dispatcher->execute(
$callback,
$params $params
); );
$dispatched = true;
if ($continue) {
self::router()->next();
} }
else { else {
break;
}
}
if (!$dispatched) {
self::notFound(); self::notFound();
} }
// Disable caching for AJAX requests // Disable caching for AJAX requests
if ($request->ajax) { if (self::request()->ajax) {
self::response()->cache(false); self::response()->cache(false);
} }

@ -0,0 +1,119 @@
<?php
/**
* Flight: An extensible micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
*/
namespace flight\net;
/**
* The Route class is responsible for routing an HTTP request to
* an assigned callback function. The Router tries to match the
* requested URL against a series of URL patterns.
*/
class Route {
/**
* @var string URL pattern
*/
public $pattern;
/**
* @var mixed Callback function
*/
public $callback;
/**
* @var array HTTP methods
*/
public $methods = array();
/**
* @var array Route parameters
*/
public $params = array();
/**
* @var string Matching regular expression
*/
public $regex;
/**
* Constructor.
*
* @param string $pattern URL pattern
* @param mixed $callback Callback function
* @param array $methods HTTP methods
*/
public function __construct($pattern, $callback, $methods) {
$this->pattern = $pattern;
$this->callback = $callback;
$this->methods = $methods;
}
/**
* Checks if a URL matches the route pattern. Also parses named parameters in the URL.
*
* @param string $url Requested URL
* @return boolean Match status
*/
public function matchUrl($url) {
if ($this->pattern === '*' || $this->pattern === $url) {
return true;
}
$ids = array();
$char = substr($this->pattern, -1);
$this->pattern = str_replace(')', ')?', $this->pattern);
// Build the regex for matching
$regex = preg_replace_callback(
'#@([\w]+)(:([^/\(\)]*))?#',
function($matches) use (&$ids) {
$ids[$matches[1]] = null;
if (isset($matches[3])) {
return '(?P<'.$matches[1].'>'.$matches[3].')';
}
return '(?P<'.$matches[1].'>[^/\?]+)';
},
$this->pattern
);
// Fix trailing slash
if ($char === '/') {
$regex .= '?';
}
// Replace wildcard
else if ($char === '*') {
$regex = str_replace('*', '.+?', $this->pattern);
}
// Allow trailing slash
else {
$regex .= '/?';
}
// Attempt to match route and named parameters
if (preg_match('#^'.$regex.'(?:\?.*)?$#i', $url, $matches)) {
foreach ($ids as $k => $v) {
$this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
}
$this->regex = $regex;
return true;
}
return false;
}
/**
* Checks if an HTTP method matches the route methods.
*
* @param string $method HTTP method
* @return bool Match status
*/
public function matchMethod($method) {
return count(array_intersect(array($method, '*'), $this->methods)) > 0;
}
}

@ -22,25 +22,11 @@ class Router {
protected $routes = array(); protected $routes = array();
/** /**
* Matched route. * Pointer to current route
* *
* @var string * @var int
*/ */
public $matched = null; protected $index = 0;
/**
* Matched URL parameters.
*
* @var array
*/
public $params = array();
/**
* Matching regular expression.
*
* @var string
*/
public $regex = null;
/** /**
* Gets mapped routes. * Gets mapped routes.
@ -52,7 +38,7 @@ class Router {
} }
/** /**
* Resets the router. * Clears all routes the router.
*/ */
public function clear() { public function clear() {
$this->routes = array(); $this->routes = array();
@ -68,89 +54,55 @@ class Router {
if (strpos($pattern, ' ') !== false) { if (strpos($pattern, ' ') !== false) {
list($method, $url) = explode(' ', trim($pattern), 2); list($method, $url) = explode(' ', trim($pattern), 2);
foreach (explode('|', $method) as $value) { $methods = explode('|', $method);
$this->routes[$value][$url] = $callback;
} array_push($this->routes, new Route($url, $callback, $methods));
} }
else { else {
$this->routes['*'][$pattern] = $callback; array_push($this->routes, new Route($pattern, $callback, array('*')));
} }
} }
/** /**
* Tries to match a request to a route. Also parses named parameters in the url. * Routes the current request.
* *
* @param string $pattern URL pattern * @param Request $request Request object
* @param string $url Requested URL * @return callable|boolean Matched callback function or false if not found
* @return boolean Match status
*/ */
public function match($pattern, $url) { public function route(Request $request) {
$ids = array(); while ($route = $this->current()) {
$char = substr($pattern, -1); if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($request->url)) {
$pattern = str_replace(')', ')?', $pattern); return $route;
// Build the regex for matching
$regex = preg_replace_callback(
'#@([\w]+)(:([^/\(\)]*))?#',
function($matches) use (&$ids) {
$ids[$matches[1]] = null;
if (isset($matches[3])) {
return '(?P<'.$matches[1].'>'.$matches[3].')';
}
return '(?P<'.$matches[1].'>[^/\?]+)';
},
$pattern
);
// Fix trailing slash
if ($char === '/') {
$regex .= '?';
}
// Replace wildcard
else if ($char === '*') {
$regex = str_replace('*', '.+?', $pattern);
}
// Allow trailing slash
else {
$regex .= '/?';
}
// Attempt to match route and named parameters
if (preg_match('#^'.$regex.'(?:\?.*)?$#i', $url, $matches)) {
foreach ($ids as $k => $v) {
$this->params[$k] = (array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
} }
$this->next();
$this->matched = $pattern;
$this->regex = $regex;
return true;
} }
return false; return false;
} }
/** /**
* Routes the current request. * Gets the current route.
* *
* @param Request $request Request object * @return Route
* @return callable|boolean Matched callback function or false if not found
*/ */
public function route(Request $request) { public function current() {
$this->matched = null; return isset($this->routes[$this->index]) ? $this->routes[$this->index] : false;
$this->regex = null;
$this->params = array();
$routes = isset($this->routes[$request->method]) ? $this->routes[$request->method] : array();
if (isset($this->routes['*'])) $routes += $this->routes['*'];
foreach ($routes as $pattern => $callback) {
if ($pattern === '*' || $request->url === $pattern || self::match($pattern, $request->url)) {
return $callback;
} }
/**
* Gets the next route.
*
* @return Route
*/
public function next() {
$this->index++;
} }
return false; /**
* Reset to the first route.
*/
public function reset() {
$this->index = 0;
} }
} }
?> ?>

@ -34,12 +34,12 @@ class RouterTest extends PHPUnit_Framework_TestCase
// Checks if a route was matched // Checks if a route was matched
function check($str = 'OK'){ function check($str = 'OK'){
$callback = $this->router->route($this->request); $route = $this->router->route($this->request);
$params = array_values($this->router->params); $params = array_values($route->params);
$this->assertTrue(is_callable($callback)); $this->assertTrue(is_callable($route->callback));
call_user_func_array($callback, $params); call_user_func_array($route->callback, $route->params);
$this->expectOutputString($str); $this->expectOutputString($str);
} }
@ -53,7 +53,7 @@ class RouterTest extends PHPUnit_Framework_TestCase
} }
// Simple path // Simple path
function testPathRoute() { function testPathRoute(){
$this->router->map('/path', array($this, 'ok')); $this->router->map('/path', array($this, 'ok'));
$this->request->url = '/path'; $this->request->url = '/path';

Loading…
Cancel
Save