* function home() {
* return 'This is the home page.';
* }
*
* function contact() {
* return 'This is the contact page.';
* }
*
* $router = new Router(array(
* '/home' => 'home',
* '/contact' => 'contact'
* ));
* $response = $router->callHandler('/home'); // 'This is the home page.'
* $response = $router->callHandler('/contact'); // 'This is the contact page.'
*
*
* You can use regular expression patterns to specify an URL. Any matches are
* passed to the handler function in a single parameter:
*
* function page(array $data) {
* $pagename = $data[0];
* return "This is the $pagename page.";
* }
*
* $router = new Router(array(
* '/(home|contact)' => 'page'
* ));
* $response = $router->callHandler('/home'); // 'This is the home page.'
* $response = $router->callHandler('/contact'); // 'This is the contact page.'
*
*
* Instead of functions, you can implement the RouteHandler interface in a
* handler class. The router will create an instance of the handler class and call *handleRequest*
*
* class MyHandler implements RouteHandler {
* function handleRequest(array $data) {
* $pagename = $data[0];
* return "This is the $pagename page.";
* }
* }
*
* $router = new Router(array(
* '/(home|contact)' => 'MyHandler'
* ));
* $response = $router->callHandler('/home'); // 'This is the home page.'
* $response = $router->callHandler('/contact'); // 'This is the contact page.'
*
*
* The WebBasics library provides a set of base handler classes implementing
* the RouteHandler interface. These are sufficient for most usage cases.
*
* @package WebBasics
*/
class Router extends Base {
/**
* The regex delimiter that is added to the begin and end of patterns.
*
* @var string
*/
const DELIMITER = '%';
/**
* An associative array of regex patterns pointing to handler functions.
*
* @var array
*/
private $routes = array();
/**
* Create a new Router instance.
*
* @param array $routes An initial list of routes to set.
*/
function __construct(array $routes=array()) {
foreach ($routes as $pattern => $handler)
$this->addRoute($pattern, $handler);
}
/**
* Add a route as a (pattern, handler) pair.
*
* The pattern is regular expression pattern without delimiters. The
* function adds '%^' at the begin and '$%' at the end of the pattern as
* delimiters.
*
* Any matches for groups in the pattern (which are contained in
* parentheses), the matches for these are passed to the handler function.
*
* @param string $pattern A regex pattern to mach URL's against.
* @param RouteHandler|callable $handler The handler function to call when $pattern is matched.
* @throws \InvalidArgumentException If $handler is not callable.
*/
function addRoute($pattern, $handler) {
if (is_callable($handler)) {
$handler = array('callable', $handler);
} else if (!is_string($handler)) {
throw new \InvalidArgumentException('Handler should be callable or class name.');
} else if (!class_exists($handler)) {
throw new \InvalidArgumentException(sprintf(
'Handler class "%s" does not exist.', $handler
));
} else if (!in_array(__NAMESPACE__ . '\RouteHandler', class_implements($handler))) {
throw new \InvalidArgumentException(sprintf(
'Handler class "%s" should implement the RouteHandler interface.', $handler
));
} else {
$handler = array('class', $handler);
}
$this->routes[self::DELIMITER . '^' . $pattern . '$' . self::DELIMITER] = $handler;
}
/**
* Call the handler function corresponding to the specified url.
*
* If any groups are in the matched regex pattern, a list of matches is
* passed to the handler function. If the handler function returns FALSE,
* the url has not been 'handled' and the next pattern will be checked for
* a match. Otherwise, the return value of the handler function is
* returned as the result.
*
* @param string $url An url to match the saved patterns against.
* @return mixed FALSE if no pattern was matched, the return value of the
* corresponding handler function otherwise.
*/
function callHandler($url) {
foreach ($this->routes as $pattern => $tuple) {
if (preg_match($pattern, $url, $matches)) {
list($type, $handler) = $tuple;
array_shift($matches);
switch ($type) {
case 'callable':
$result = count($matches) ? $handler($matches) : $handler();
break;
case 'class':
$instance = new $handler;
$result = $instance->handleRequest($matches);
}
if ($result !== false)
return $result;
}
}
return false;
}
}
/**
* Interface for handler classes which can be bound to a router pattern.
*
* @package WebBasics
*/
interface RouteHandler {
/**
* Handle an HTTP request.
*
* @param string[] $data A list of matched pattern groups, without the zero-group.
* @return mixed FALSE if the handler function was not able to handle the
* request, else the result of the reqeust.
*/
function handleRequest(array $data);
}
?>