router.php 3.53 KB
Newer Older
Taddeus Kroes's avatar
Taddeus Kroes committed
1 2 3 4 5 6 7 8 9
<?php
/**
 * Functions for URL routing: given an URL, call the corresponding handler
 * function (the 'route' to the corresponding output).
 * 
 * @author Taddeus Kroes
 * @date 14-07-2012
 */

Taddeus Kroes's avatar
Taddeus Kroes committed
10
namespace webbasics;
Taddeus Kroes's avatar
Taddeus Kroes committed
11 12 13 14

require_once 'base.php';

/**
Taddeus Kroes's avatar
Taddeus Kroes committed
15
 * A Router is used to call a handler function with corresponding to an URL.
Taddeus Kroes's avatar
Taddeus Kroes committed
16
 * 
Taddeus Kroes's avatar
Taddeus Kroes committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30
 * Simple example: a website with the pages 'home' and 'contact'.
 * <code>
 * function home() {
 *     return 'This is the home page.';
 * }
 * 
 * function contact() {
 *     return 'This is the contact page.';
 * }
 * 
 * $router = new Router(array(
 *     '/home' => 'home',
 *     '/contact' => 'contact'
 * ));
Taddeus Kroes's avatar
Taddeus Kroes committed
31 32
 * $response = $router->callHandler('/home');     // 'This is the home page.'
 * $response = $router->callHandler('/contact');  // 'This is the contact page.'
Taddeus Kroes's avatar
Taddeus Kroes committed
33
 * </code>
Taddeus Kroes's avatar
Taddeus Kroes committed
34
 * 
Taddeus Kroes's avatar
Taddeus Kroes committed
35
 * You can use regular expression patterns to specify an URL. Any matches are
Taddeus Kroes's avatar
Taddeus Kroes committed
36
 * passed to the handler function as parameters:
Taddeus Kroes's avatar
Taddeus Kroes committed
37
 * <code>
Taddeus Kroes's avatar
Taddeus Kroes committed
38 39 40
 * function page($pagename) {
 *     return "This is the $pagename page.";
 * }
Taddeus Kroes's avatar
Taddeus Kroes committed
41
 * 
Taddeus Kroes's avatar
Taddeus Kroes committed
42 43 44
 * $router = new Router(array(
 *     '/(home|contact)' => 'page'
 * ));
Taddeus Kroes's avatar
Taddeus Kroes committed
45 46
 * $response = $router->callHandler('/home');     // 'This is the home page.'
 * $response = $router->callHandler('/contact');  // 'This is the contact page.'
Taddeus Kroes's avatar
Taddeus Kroes committed
47 48
 * </code>
 * 
49
 * @package WebBasics
Taddeus Kroes's avatar
Taddeus Kroes committed
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
 */
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()) {
72
		foreach ($routes as $pattern => $handler)
Taddeus Kroes's avatar
Taddeus Kroes committed
73
			$this->addRoute($pattern, $handler);
Taddeus Kroes's avatar
Taddeus Kroes committed
74 75 76 77 78 79 80 81 82
	}
	
	/**
	 * 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.
	 * 
Taddeus Kroes's avatar
Taddeus Kroes committed
83 84
	 * Any matches for groups in the pattern (which are contained in
	 * parentheses), the matches for these are passed to the handler function.
Taddeus Kroes's avatar
Taddeus Kroes committed
85 86 87 88 89
	 * 
	 * @param string $pattern A regex pattern to mach URL's against.
	 * @param mixed $handler The handler function to call when $pattern is matched.
	 * @throws \InvalidArgumentException If $handler is not callable.
	 */
Taddeus Kroes's avatar
Taddeus Kroes committed
90
	function addRoute($pattern, $handler) {
91
		if (!is_callable($handler))
Taddeus Kroes's avatar
Taddeus Kroes committed
92 93
			throw new \InvalidArgumentException(sprintf('Handler for patterns "%s" is not callable.', $pattern));
		
94
		$this->routes[self::DELIMITER . '^' . $pattern . '$' . self::DELIMITER] = $handler;
Taddeus Kroes's avatar
Taddeus Kroes committed
95 96 97 98 99 100
	}
	
	/**
	 * Call the handler function corresponding to the specified url.
	 * 
	 * If any groups are in the matched regex pattern, a list of matches is
101 102 103 104
	 * 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.
Taddeus Kroes's avatar
Taddeus Kroes committed
105 106 107 108 109
	 * 
	 * @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.
	 */
Taddeus Kroes's avatar
Taddeus Kroes committed
110
	function callHandler($url) {
111 112
		foreach ($this->routes as $pattern => $handler) {
			if (preg_match($pattern, $url, $matches)) {
Taddeus Kroes's avatar
Taddeus Kroes committed
113
				array_shift($matches);
114 115
				$result = call_user_func_array($handler, $matches);
				
116
				if ($result !== false)
117
					return $result;
Taddeus Kroes's avatar
Taddeus Kroes committed
118 119 120 121 122 123 124 125
			}
		}
		
		return false;
	}
}

?>