Browse Source

Added RouteHandler functionaity to Router class

Taddeus Kroes 13 năm trước cách đây
mục cha
commit
8c329e4c42
2 tập tin đã thay đổi với 116 bổ sung12 xóa
  1. 67 8
      router.php
  2. 49 4
      tests/test_router.php

+ 67 - 8
router.php

@@ -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);
+}
+
 ?>

+ 49 - 4
tests/test_router.php

@@ -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');
+	}
 }
 
 ?>