* @link http://www.pradosoft.com/ * @copyright Copyright © 2006 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @package System.Web */ Prado::using('System.Web.TUrlManager'); /** * TUrlMapping Class * * The TUrlMapping module allows aributary URL path to be mapped to a * particular service and page class. This module must be configured * before a service is initialized, thus this module should be configured * globally in the application.xml file and before any services. * * * * * * * * * * See {@link TUrlMappingPattern} for details regarding the mapping patterns. * Similar to other modules, the <url /> configuration class * can be customised using the class property. * * The URL mapping are evaluated in order, only the first mapping that matches * the URL will be used. Cascaded mapping can be achieved by placing the URL mappings * in particular order. For example, placing the most specific mappings first. * * The mapping can be load from an external file by specifying a configuration * file using the {@link setConfigFile ConfigFile} property. * * Since TUrlMapping is a URL manager extending from {@link TUrlManager}, * you may override {@link TUrlManager::constructUrl} to support your pattern-based * URL scheme. * * @author Wei Zhuo * @version $Id$ * @package System.Web * @since 3.0.5 */ class TUrlMapping extends TUrlManager { /** * File extension of external configuration file */ const CONFIG_FILE_EXT='.xml'; /** * @var string default pattern class. */ private $_defaultPatternClass='TUrlMappingPattern'; /** * @var TUrlMappingPattern[] list of patterns. */ private $_patterns=array(); /** * @var TUrlMappingPattern matched pattern. */ private $_matched; /** * @var string external configuration file */ private $_configFile=null; /** * Initializes this module. * This method is required by the IModule interface. * @param TXmlElement configuration for this module, can be null * @throws TConfigurationException if module is configured in the global scope. */ public function init($xml) { parent::init($xml); if($this->getRequest()->getRequestResolved()) throw new TConfigurationException('urlpath_dispatch_module_must_be_global'); if($this->_configFile!==null) $this->loadConfigFile(); $this->loadUrlMappings($xml); } /** * Initialize the module from configuration file. * @throws TConfigurationException if {@link getConfigFile ConfigFile} is invalid. */ protected function loadConfigFile() { if(is_file($this->_configFile)) { $dom=new TXmlDocument; $dom->loadFromFile($this->_configFile); $this->loadUrlMappings($dom); } else throw new TConfigurationException( 'urlpath_dispatch_configfile_invalid',$this->_configFile); } /** * @return string external configuration file. Defaults to null. */ public function getConfigFile() { return $this->_configFile; } /** * @param string external configuration file in namespace format. The file * must be suffixed with '.xml'. * @throws TInvalidDataValueException if the file is invalid. */ public function setConfigFile($value) { if(($this->_configFile=Prado::getPathOfNamespace($value,self::CONFIG_FILE_EXT))===null) throw new TConfigurationException('urlpath_configfile_invalid',$value); } /** * Load and configure each url mapping pattern. * @param TXmlElement configuration node * @throws TConfigurationException if specific pattern class is invalid */ protected function loadUrlMappings($xml) { foreach($xml->getElementsByTagName('url') as $url) { $properties=$url->getAttributes(); $class=$properties->remove('class'); if($class===null) $class = $this->_defaultPatternClass; $pattern = Prado::createComponent($class); if(!($pattern instanceof TUrlMappingPattern)) throw new TConfigurationException('urlpath_dispatch_invalid_pattern_class'); foreach($properties as $name=>$value) $pattern->setSubproperty($name,$value); $this->_patterns[] = $pattern; $pattern->init($url); } } /** * Parses the request URL and returns an array of input parameters. * This method overrides the parent implementation. * The input parameters do not include GET and POST variables. * This method uses the request URL path to find the first matching pattern. If found * the matched pattern parameters are used to return as the input parameters. * @return array list of input parameters */ public function parseUrl() { $url = $this->getRequest()->getUrl(); foreach($this->_patterns as $pattern) { $matches = $pattern->getPatternMatches($url); if(count($matches) > 0) { $this->_matched=$pattern; $this->changeServiceParameters($pattern); $params=array(); foreach($matches as $key=>$value) if(is_string($key)) $params[$key]=$value; return $params; } } return parent::parseUrl(); } /** * @return TUrlMappingPattern the matched pattern, null if not found. */ public function getMatchingPattern() { return $this->_matched; } /** * @param TUrlMappingPattern change the Request service ID and page class. */ protected function changeServiceParameters($pattern) { $request = $this->getRequest(); $id = $pattern->getServiceID(); $param = $pattern->getServiceParameter(); $request->setServiceID($id); $request->setServiceParameter($param); $request->add($id,$param); } } /** * URL Mapping Pattern Class * * Describes an URL mapping pattern, if a given URL matches the pattern, the * TUrlMapping class will alter the THttpRequest parameters. The * url matching is done using patterns and regular expressions. * * The {@link setPattern Pattern} property takes an string expression with * parameter names enclosed between a left brace '{' and a right brace '}'. * The pattens for each parameter can be set using {@link getParameters Parameters} * attribute collection. For example * * * * * In the above example, the pattern contains 3 parameters named "year", * "month" and "day". The pattern for these parameters are, respectively, * "\d{4}" (4 digits), "\d{2}" (2 digits) and "\d+" (1 or more digits). * Essentially, the Parameters attribute name and values are used * as substrings in replacing the placeholders in the Pattern string * to form a complete regular expression string. A full regular expression * may be expressed using the RegularExpression attribute or * as the body content of the <module> tag. The above pattern is equivalent * to the following regular expression pattern. * * /articles\/(?P\d{4})\/(?P\d{2})\/(?P\d+)/u * * The above regular expression used the "named group" feature available in PHP. * Notice that you need to escape the slash in regular expressions. * * In the TUrlMappingPattern class, the pattern is matched against the * path property of the url only. * * Thus, only an url that matches the pattern will be valid. For example, * an url "http://example.com/articles/2006/07/21" will matches and is valid. * However, "http://example.com/articles/2006/07/hello" is not * valid since the "day" parameter pattern is not satisfied. * * The parameter values are available through the standard Request * object. For example, $this->Request['year']. * * The {@link setServiceParameter ServiceParameter} and {@link setServiceID ServiceID} * (the default ID is 'page') set the service parameter and service id respectively. * The service parameter for the TPageService is the Page class name, other service * may use the service parameter differently. * * For more complicated mappings, the body of the <url> * can be used to specify the mapping pattern. * * @author Wei Zhuo * @version $Id$ * @package System.Web * @since 3.0.5 */ class TUrlMappingPattern extends TComponent { /** * @var string service parameter such as Page class name. */ private $_serviceParameter; /** * @var string service ID, default is 'page'. */ private $_serviceID='page'; /** * @var string url pattern to match. */ private $_pattern; /** * @var TMap parameter regular expressions. */ private $_parameters; /** * @var string regular expression pattern. */ private $_regexp; /** * @var boolean case sensitive matching, default is true */ private $_caseSensitive=true; public function __construct() { $this->_parameters = Prado::createComponent('System.Collections.TAttributeCollection'); } /** * Initialize the pattern, uses the body content as pattern is available. * @param TXmlElement configuration for this module. * @throws TConfigurationException if page class is not specified. */ public function init($config) { $body = trim($config->getValue()); if(strlen($body)>0) $this->setRegularExpression($body); if(is_null($this->_serviceParameter)) { throw new TConfigurationException( 'dispatcher_url_service_parameter_missing', $this->getPattern()); } } /** * Subsitutue the parameter key value pairs as named groupings * in the regular expression matching pattern. * @return string regular expression pattern with parameter subsitution */ protected function getParameterizedPattern() { $params= array(); $values = array(); foreach($this->parameters as $key => $value) { $params[] = '{'.$key.'}'; $values[] = '(?P<'.$key.'>'.$value.')'; } $params[] = '/'; $values[] = '\\/'; $regexp = str_replace($params,$values,$this->getPattern()); $modifiers = $this->getModifiers(); return '/'.$regexp.'/'.$modifiers; } /** * @return string full regular expression mapping pattern */ public function getRegularExpression() { return $this->_regexp; } /** * @param string full regular expression mapping patern. */ public function setRegularExpression($value) { $this->_regexp=$value; } /** * @param string service parameter, such as page class name. */ public function setServiceParameter($value) { $this->_serviceParameter=$value; } /** * @return string service parameter, such as page class name. */ public function getServiceParameter() { return $this->_serviceParameter; } /** * @param string service id to handle. */ public function setServiceID($value) { $this->_serviceID=$value; } /** * @return string service id. */ public function getServiceID() { return $this->_serviceID; } /** * @return string url pattern to match. */ public function getPattern() { return $this->_pattern; } /** * @param string url pattern to match. */ public function setPattern($value) { $this->_pattern = $value; } /** * @param boolean case sensitive pattern matching, default is true. */ public function setCaseSensitive($value) { $this->_caseSensitive=TPropertyValue::ensureBoolean($value); } /** * @return boolean case sensitive pattern matching, default is true. */ public function getCaseSensitive() { return $this->_caseSensitive; } /** * @return TAttributeCollection parameter key value pairs. */ public function getParameters() { return $this->_parameters; } /** * @param TAttributeCollection new parameter key value pairs. */ public function setParameters($value) { $this->_parameters=$value; } /** * Uses URL pattern (or full regular expression if available) to * match the given url path. * @param TUri url to match against * @return array matched parameters, empty if no matches. */ public function getPatternMatches($url) { $path = $url->getPath(); $matches=array(); $pattern = $this->getRegularExpression(); if($pattern === null) $pattern = $this->getParameterizedPattern(); preg_match($pattern, $path, $matches); return $matches; } /** * @return string regular expression matching modifiers. */ protected function getModifiers() { $modifiers = 'u'; if(!$this->getCaseSensitive()) $modifiers .= 'i'; return $modifiers; } } ?>