* @link http://www.pradosoft.com/ * @copyright Copyright © 2006 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @package System.Web */ /** * 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. * * The mapping format is as follows. * * * * * * * * * 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. * * @author Wei Zhuo * @version $Id$ * @package System.Web * @since 3.0.5 */ class TUrlMapping extends THttpRequest { /** * @var string default pattern class. */ private $_defaultPatternClass='TUrlMappingPattern'; /** * @var TUrlMappingPattern[] list of patterns. */ private $_patterns=array(); /** * @var TUrlMappingPattern matched pattern. */ private $_matched; /** * File extension of external configuration file */ const CONFIG_FILE_EXT='.xml'; /** * @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) { if($this->getRequest()->getRequestResolved()) throw new TConfigurationException('urlpath_dispatch_module_must_be_global'); if($this->_configFile!==null) $this->loadConfigFile(); $this->loadUrlMappings($xml); $this->resolveMappings(); } /** * 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('logrouter_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); } } /** * Using the request URL path, find the first matching pattern. If found * the matched pattern parameters are used in the Request object. */ protected function resolveMappings() { $url = $this->getRequest()->getUrl(); foreach($this->_patterns as $pattern) { $matches = $pattern->getPatternMatches($url); if(count($matches) > 0) { $this->changeServiceParameters($pattern); $this->initializeRequestParameters($matches); $this->_matched=$pattern; break; } } } /** * @return TUrlMappingPattern the matched pattern, null if not found. */ public function getMatchingPattern() { return $this->_matched; } /** * @param array initialize the Request with matched parameters. */ protected function initializeRequestParameters($matches) { $request = $this->getRequest(); foreach($matches as $k => $v) { if(!is_int($k)) $request->add($k,$v); } } /** * @param TUrlMappingPattern change the Request service ID and page class. */ protected function changeServiceParameters($pattern) { $request = $this->getRequest(); $id = $pattern->getServiceID(); $page = $pattern->getPageClass(); $request->setServiceID($id); $request->setServiceParameter($page); $request->add($id,$page); } } /** * 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 regular expressions. * * The {@link setPattern Pattern} property takes a regular 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). * * 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 setPageClass PageClass} and {@link setServiceID ServiceID} * (the default ID is 'page') set the class and the service that will * handle the matching URL. * * 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 page class name. */ private $_pageClass; /** * @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; 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->setPattern($body); if(is_null($this->_pageClass)) { throw new TConfigurationException( 'dispatcher_url_page_class_missing', $this->getPattern()); } $this->initializePattern(); } /** * Subsitutue the parameter key value pairs as named groupings * in the regular expression matching pattern. */ protected function initializePattern() { $params= array(); $values = array(); foreach($this->parameters as $key => $value) { $params[] = '{'.$key.'}'; $values[] = '(?P<'.$key.'>'.$value.')'; } $this->_regexp = str_replace($params,$values,$this->getPattern()); } /** * @return string mapping pattern */ protected function getRegExpPattern() { return $this->_regexp; } /** * @param string name of the page class to handle the request. */ public function setPageClass($value) { $this->_pageClass=$value; } /** * @return string page class name. */ public function getPageClass() { return $this->_pageClass; } /** * @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; } /** * @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; } /** * Use regular expression 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 = str_replace('/', '\\/', $this->getRegExpPattern()); preg_match('/'.$pattern.'/', $path, $matches); return $matches; } } ?>