rendered paste body<?php/** * @package lib374c *//** * Lib374C_Exception */require_once 'Lib374C/Exception.php';/** * Lib374C_MVC_Exception */require_once 'Lib374C/MVC/Exception.php';/** * URL routing class * * @package lib374c */class Lib374C_MVC_URLRouter{ protected static $_instance; protected $_routes = array(); protected $_defaultRoute = null; protected $_defaultRouteParsed = null; protected $_baseUrl = ''; /** * Connect a url to some parameters * * @param string $url * @param array $defaults */ public function connect($url, $defaults = array(), $requirements = array()) { $url = preg_replace('|^/|', '', $url); // parse route extract($this->_parseRoute($url, $defaults, $requirements)); // $regex, $begin_regex, $param_names, $offsets // store route $route = new StdClass; $route->url = $url; $route->regex = $regex; $route->begin_regex = $begin_regex; $route->defaults = $defaults; $route->param_names = $param_names; $route->requirements = $requirements; $route->offsets = $offsets; array_unshift($this->_routes, $route); } /** * Parse a route. * * returns: array( * 'regex' => '`^...$`i', * 'begin_regex' => '`^...(?:/\|$)`i', * 'param_names' => array('a', 'b', 'c'), * 'offsets' => array( * array(0, 10), // $offset, $length * array(16, 2), * ), * ) * * @param string $route * @param array $defaults * @param array $requirements * @return array */ protected function _parseRoute($route, $defaults = array(), $requirements = array()) { $regex = str_replace("\\:", ":", preg_quote($route, '`')); preg_match_all('|:([0-9A-Za-z_]+)|', $regex, $url_param_matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); $offset_adjust = 0; $param_names = array(); $offsets = array(); foreach($url_param_matches as $match) { $param = new StdClass; $param->name = $match[1][0]; $param->offset = $match[0][1]; $param->length = strlen($match[0][0]); // check duplicate if(in_array($param->name, $param_names)) throw new Lib374C_MVC_Exception('Duplicate param in url: [' . $param->name . ']'); if(isset($defaults[$param->name])) throw new Lib374C_MVC_Exception('Cannot set default value for parameter defined in URL'); // generate regex $old_length = strlen($regex); $regex = substr_replace($regex, '(?<' . $param->name . '>[^/]*?)', $param->offset + $offset_adjust, $param->length); $new_length = strlen($regex); $offset_adjust += $new_length - $old_length; // store offset $offsets[$param->name] = array($param->offset, $param->length); $param_names[] = $param->name; } $begin_regex = '`^' . $regex . '(?<__REST__>/.*|)$`i'; $regex = '`^' . $regex . '$`i'; foreach(array_keys($requirements) as $key) if(!in_array($key, $param_names)) throw new Lib374C_MVC_Exception('Can\'t set requirement for non-existent param'); return array( 'regex' => $regex, 'begin_regex' => $begin_regex, 'param_names' => $param_names, 'offsets' => $offsets, ); } /** * Route a url * * @param string $url * @return array */ public function route($url) { $url = preg_replace('|^/|', '', $url); foreach($this->_routes as $route) { if(preg_match($route->regex, $url, $matches)) { $params = $route->defaults; // get parameters foreach($route->param_names as $name) { $params[$name] = urldecode($matches[$name]); } // check requirements foreach($route->requirements as $name => $req) { if(!preg_match($req, $params[$name])) { continue 2; } } return $params; } } if($this->_defaultRoute !== null && preg_match($this->_defaultRouteParsed['begin_regex'], $url, $matches)) { $params = array(); foreach($this->_defaultRouteParsed['param_names'] as $name) $params[$name] = $matches[$name]; $rest = explode("/", $matches['__REST__']); foreach($rest as $rest_val) { if(strpos($rest_val, '=') === false) continue; list($key, $val) = array_map('urldecode', explode('=', $rest_val, 2)); $params[$key] = $val; } return $params; } throw new Lib374C_MVC_Route_Not_Found_Exception('Couldn\'t route that URL'); } /** * Generate a url * * @param array $params * @return string */ public function assemble($params) { $keys = array_keys($params); foreach($this->_routes as $route) { foreach($keys as $key) if(!in_array($key, $route->param_names) && !(isset($route->defaults[$key]) && $route->defaults[$key] == $params[$key])) continue 2; // next route foreach($route->requirements as $key => $req) if(!preg_match($req, $params[$key])) continue 2; // next route $output = $route->url; $offset_adjust = 0; foreach($params as $key => $value) { if(!isset($route->defaults[$key])) { $old_length = strlen($output); $output = substr_replace($output, urlencode($value), $route->offsets[$key][0] + $offset_adjust, $route->offsets[$key][1]); $offset_adjust += strlen($output) - $old_length; } } return $this->_baseUrl . $output; } if($this->_defaultRoute !== null) { // all required parameters given? foreach($this->_defaultRouteParsed['param_names'] as $param_name) if(!isset($params[$param_name])) throw new Lib374C_MVC_Exception('Specify param [' . $param_name . ']'); // generate url $url = $this->_defaultRoute; $offset_adjust = 0; foreach($this->_defaultRouteParsed['offsets'] as $param_name => $value) { list($offset, $length) = $value; $old_length = strlen($url); $url = substr_replace($url, urlencode($params[$param_name]), $offset + $offset_adjust, $length); $offset_adjust += strlen($url) - $old_length; unset($params[$param_name]); } foreach($params as $key => $value) $url .= "/" . urlencode($key) . "=" . urlencode($value); return $this->_baseUrl . $url; } else { throw new Lib374C_MVC_Exception('No such route, and no default route set'); } } /** * Set or remove default route * * @param string|null $route */ public function setDefaultRoute($route) { if($route !== null) $route = preg_replace('|^/*(.*?)/*$|', '$1', $route); $this->_defaultRoute = $route; if($route !== null) $this->_defaultRouteParsed = $this->_parseRoute($route); } /** * Set the instance retrieved by getInstance() * * @param Lib374C_MVC_URLRouter $router */ public static function setInstance(Lib374C_MVC_URLRouter $router) { self::$_instance = $router; } /** * Get instance set by setInstance() * * @return Lib374C_MVC_URLRouter */ public static function getInstance() { if(!isset(self::$_instance)) throw new Lib374C_MVC_Exception('Use setInstance($router) before calling getInstance()'); return self::$_instance; } /** * Set base url * * @param string $url */ public function setBaseUrl($url) { $this->_baseUrl = $url; } /** * Connect routes from array. * * array( * 'route1' => array( * 'url' => 'test/:param', * 'params' => array( // optional * 'controller' => 'testController', * 'action' => 'testAction', * ), * 'requirements' => array( // optional * 'param' => '/^\d+$/', * ), * ), * ) * * @param array $routes */ public function routesFromArray($routes) { foreach(array_reverse($routes) as $key => $route) { if(!isset($route['url'])) throw new Lib374C_MVC_Exception("routes[$key]: [url] not set"); if(!isset($route['params'])) $route['params'] = array(); if(!isset($route['requirements'])) $route['requirements'] = array(); $this->connect($route['url'], $route['params'], $route['requirements']); } }}