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
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
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){
echo 'two';
// This will end the chain
// This will end the chain
return false;
});

@ -259,25 +259,31 @@ class Flight {
* Starts the framework.
*/
public static function _start() {
$router = self::router();
$request = self::request();
$dispatched = false;
// Route the request
$callback = $router->route($request);
if ($callback !== false) {
$params = array_values($router->params);
self::$dispatcher->execute(
$callback,
while ($route = self::router()->route(self::request())) {
$params = array_values($route->params);
$continue = self::$dispatcher->execute(
$route->callback,
$params
);
$dispatched = true;
if ($continue) {
self::router()->next();
}
else {
break;
}
}
if (!$dispatched) {
self::notFound();
}
// Disable caching for AJAX requests
if ($request->ajax) {
if (self::request()->ajax) {
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();
/**
* Matched route.
* Pointer to current route
*
* @var string
* @var int
*/
public $matched = null;
/**
* Matched URL parameters.
*
* @var array
*/
public $params = array();
/**
* Matching regular expression.
*
* @var string
*/
public $regex = null;
protected $index = 0;
/**
* Gets mapped routes.
@ -52,7 +38,7 @@ class Router {
}
/**
* Resets the router.
* Clears all routes the router.
*/
public function clear() {
$this->routes = array();
@ -68,89 +54,55 @@ class Router {
if (strpos($pattern, ' ') !== false) {
list($method, $url) = explode(' ', trim($pattern), 2);
foreach (explode('|', $method) as $value) {
$this->routes[$value][$url] = $callback;
}
$methods = explode('|', $method);
array_push($this->routes, new Route($url, $callback, $methods));
}
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 string $url Requested URL
* @return boolean Match status
* @param Request $request Request object
* @return callable|boolean Matched callback function or false if not found
*/
public function match($pattern, $url) {
$ids = array();
$char = substr($pattern, -1);
$pattern = str_replace(')', ')?', $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].'>[^/\?]+)';
},
$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;
public function route(Request $request) {
while ($route = $this->current()) {
if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($request->url)) {
return $route;
}
$this->matched = $pattern;
$this->regex = $regex;
return true;
$this->next();
}
return false;
}
/**
* Routes the current request.
* Gets the current route.
*
* @param Request $request Request object
* @return callable|boolean Matched callback function or false if not found
* @return Route
*/
public function route(Request $request) {
$this->matched = null;
$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;
public function current() {
return isset($this->routes[$this->index]) ? $this->routes[$this->index] : false;
}
/**
* 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
function check($str = 'OK'){
$callback = $this->router->route($this->request);
$params = array_values($this->router->params);
$route = $this->router->route($this->request);
$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);
}
@ -53,7 +53,7 @@ class RouterTest extends PHPUnit_Framework_TestCase
}
// Simple path
function testPathRoute() {
function testPathRoute(){
$this->router->map('/path', array($this, 'ok'));
$this->request->url = '/path';

Loading…
Cancel
Save