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

Added RouteHandler functionaity to Router class

parent 1677e70b
...@@ -12,7 +12,7 @@ namespace webbasics; ...@@ -12,7 +12,7 @@ namespace webbasics;
require_once 'base.php'; 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'. * Simple example: a website with the pages 'home' and 'contact'.
* <code> * <code>
...@@ -33,9 +33,10 @@ require_once 'base.php'; ...@@ -33,9 +33,10 @@ require_once 'base.php';
* </code> * </code>
* *
* You can use regular expression patterns to specify an URL. Any matches are * 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> * <code>
* function page($pagename) { * function page(array $data) {
* $pagename = $data[0];
* return "This is the $pagename page."; * return "This is the $pagename page.";
* } * }
* *
...@@ -46,6 +47,26 @@ require_once 'base.php'; ...@@ -46,6 +47,26 @@ require_once 'base.php';
* $response = $router->callHandler('/contact'); // 'This is the contact page.' * $response = $router->callHandler('/contact'); // 'This is the contact page.'
* </code> * </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 * @package WebBasics
*/ */
class Router extends Base { class Router extends Base {
...@@ -84,12 +105,25 @@ class Router extends Base { ...@@ -84,12 +105,25 @@ class Router extends Base {
* parentheses), the matches for these are passed to the handler function. * parentheses), the matches for these are passed to the handler function.
* *
* @param string $pattern A regex pattern to mach URL's against. * @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. * @throws \InvalidArgumentException If $handler is not callable.
*/ */
function addRoute($pattern, $handler) { function addRoute($pattern, $handler) {
if (!is_callable($handler)) if (is_callable($handler)) {
throw new \InvalidArgumentException(sprintf('Handler for patterns "%s" is not callable.', $pattern)); $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; $this->routes[self::DELIMITER . '^' . $pattern . '$' . self::DELIMITER] = $handler;
} }
...@@ -108,10 +142,19 @@ class Router extends Base { ...@@ -108,10 +142,19 @@ class Router extends Base {
* corresponding handler function otherwise. * corresponding handler function otherwise.
*/ */
function callHandler($url) { function callHandler($url) {
foreach ($this->routes as $pattern => $handler) { foreach ($this->routes as $pattern => $tuple) {
if (preg_match($pattern, $url, $matches)) { if (preg_match($pattern, $url, $matches)) {
list($type, $handler) = $tuple;
array_shift($matches); 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) if ($result !== false)
return $result; return $result;
...@@ -122,4 +165,20 @@ class Router extends Base { ...@@ -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 @@ ...@@ -2,19 +2,31 @@
require_once 'router.php'; require_once 'router.php';
use webbasics\Router; use webbasics\Router;
use webbasics\RouteHandler;
function test_handler_no_args() { function test_handler_no_args() {
return true; return true;
} }
function test_handler_arg($arg) { function test_handler_arg(array $args) {
return $arg; return $args[0];
} }
function test_handler_args($arg0, $arg1) { function test_handler_args(array $args) {
list($arg0, $arg1) = $args;
return $arg1 . $arg0; 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 { class RouterTest extends PHPUnit_Framework_TestCase {
function setUp() { function setUp() {
$this->router = new Router(array( $this->router = new Router(array(
...@@ -44,10 +56,43 @@ class RouterTest extends PHPUnit_Framework_TestCase { ...@@ -44,10 +56,43 @@ class RouterTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('bar', $foo); $this->assertEquals('bar', $foo);
} }
function testAddRoute() { function testAddRouteCallableSuccess() {
$this->router->addRoute('(foobar)', 'test_handler_arg'); $this->router->addRoute('(foobar)', 'test_handler_arg');
$this->assertEquals('foobar', $this->router->callHandler('foobar')); $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