Commit 8c329e4c authored by Taddeus Kroes's avatar Taddeus Kroes

Added RouteHandler functionaity to Router class

parent 1677e70b
......@@ -12,7 +12,7 @@ namespace webbasics;
require_once 'base.php';
/**
* A Router is used to call a handler function with corresponding to an URL.
* A Router is used to call a handler with corresponding to an URL.
*
* Simple example: a website with the pages 'home' and 'contact'.
* <code>
......@@ -33,9 +33,10 @@ require_once 'base.php';
* </code>
*
* You can use regular expression patterns to specify an URL. Any matches are
* passed to the handler function as parameters:
* passed to the handler function in a single parameter:
* <code>
* function page($pagename) {
* function page(array $data) {
* $pagename = $data[0];
* return "This is the $pagename page.";
* }
*
......@@ -46,6 +47,26 @@ require_once 'base.php';
* $response = $router->callHandler('/contact'); // 'This is the contact page.'
* </code>
*
* 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*
* <code>
* 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.'
* </code>
*
* 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 {
......@@ -84,12 +105,25 @@ class Router extends Base {
* parentheses), the matches for these are passed to the handler function.
*
* @param string $pattern A regex pattern to mach URL's against.
* @param mixed $handler The handler function to call when $pattern is matched.
* @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))
throw new \InvalidArgumentException(sprintf('Handler for patterns "%s" is not callable.', $pattern));
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;
}
......@@ -108,10 +142,19 @@ class Router extends Base {
* corresponding handler function otherwise.
*/
function callHandler($url) {
foreach ($this->routes as $pattern => $handler) {
foreach ($this->routes as $pattern => $tuple) {
if (preg_match($pattern, $url, $matches)) {
list($type, $handler) = $tuple;
array_shift($matches);
$result = call_user_func_array($handler, $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;
......@@ -122,4 +165,20 @@ class Router extends Base {
}
}
/**
* 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);
}
?>
\ No newline at end of file
......@@ -2,19 +2,31 @@
require_once 'router.php';
use webbasics\Router;
use webbasics\RouteHandler;
function test_handler_no_args() {
return true;
}
function test_handler_arg($arg) {
return $arg;
function test_handler_arg(array $args) {
return $args[0];
}
function test_handler_args($arg0, $arg1) {
function test_handler_args(array $args) {
list($arg0, $arg1) = $args;
return $arg1 . $arg0;
}
class TestHandler implements RouteHandler {
function handleRequest(array $data) {
return $data[0];
}
}
class InterfacelessHandler {
function handleRequest(array $data) {}
}
class RouterTest extends PHPUnit_Framework_TestCase {
function setUp() {
$this->router = new Router(array(
......@@ -44,10 +56,43 @@ class RouterTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('bar', $foo);
}
function testAddRoute() {
function testAddRouteCallableSuccess() {
$this->router->addRoute('(foobar)', 'test_handler_arg');
$this->assertEquals('foobar', $this->router->callHandler('foobar'));
}
/**
* @expectedException \InvalidArgumentException
*/
function testAddRouteCallableFailure() {
$this->router->addRoute('(foobar)', 'non_existing_function');
}
function testAddRouteHandlerSuccess() {
$this->router->addRoute('(foobar)', 'TestHandler');
$this->assertEquals('foobar', $this->router->callHandler('foobar'));
}
/**
* @expectedException \InvalidArgumentException
*/
function testAddRouteHandlerNoString() {
$this->router->addRoute('(foobar)', new TestHandler);
}
/**
* @expectedException \InvalidArgumentException
*/
function testAddRouteHandlerNonExisting() {
$this->router->addRoute('(foobar)', 'NonExistingHandler');
}
/**
* @expectedException \InvalidArgumentException
*/
function testAddRouteHandlerWithoutInterface() {
$this->router->addRoute('(foobar)', 'InterfacelessHandler');
}
}
?>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment