mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			558 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			558 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
 | |
| namespace Test\Router;
 | |
| 
 | |
| /**
 | |
|  * Test\Router\Route
 | |
|  *
 | |
|  * This class represents every route added to the router
 | |
|  */
 | |
| class Route
 | |
| {
 | |
|     protected _pattern;
 | |
| 
 | |
|     protected _compiledPattern;
 | |
| 
 | |
|     protected _paths;
 | |
| 
 | |
|     protected _methods;
 | |
| 
 | |
|     protected _hostname;
 | |
| 
 | |
|     protected _converters;
 | |
| 
 | |
|     protected _id;
 | |
| 
 | |
|     protected _name;
 | |
| 
 | |
|     protected _beforeMatch;
 | |
| 
 | |
|     /**
 | |
|      * Test\Router\Route constructor
 | |
|      *
 | |
|      * @param string pattern
 | |
|      * @param array paths
 | |
|      * @param array|string httpMethods
 | |
|      */
 | |
|     public function __construct(pattern, paths=null, httpMethods=null)
 | |
|     {
 | |
|         // Configure the route (extract parameters, paths, etc)
 | |
|         this->reConfigure(pattern, paths);
 | |
| 
 | |
|         // Update the HTTP method constraints
 | |
|         let this->_methods = httpMethods;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Replaces placeholders from pattern returning a valid PCRE regular expression
 | |
|      *
 | |
|      * @param string pattern
 | |
|      * @return string
 | |
|      */
 | |
|     public function compilePattern(pattern)
 | |
|     {
 | |
|         var idPattern;
 | |
| 
 | |
|         // If a pattern contains ':', maybe there are placeholders to replace
 | |
|         if memstr(pattern, ":") {
 | |
| 
 | |
|             // This is a pattern for valid identifiers
 | |
|             let idPattern = "/([a-zA-Z0-9\\_\\-]+)";
 | |
| 
 | |
|             // Replace the module part
 | |
|             if memstr(pattern, "/:module") {
 | |
|                 let pattern = str_replace("/:module", idPattern, pattern);
 | |
|             }
 | |
| 
 | |
|             // Replace the controller placeholder
 | |
|             if memstr(pattern, "/:controller") {
 | |
|                 let pattern = str_replace("/:controller", idPattern, pattern);
 | |
|             }
 | |
| 
 | |
|             // Replace the namespace placeholder
 | |
|             if memstr(pattern, "/:namespace") {
 | |
|                 let pattern = str_replace("/:namespace", idPattern, pattern);
 | |
|             }
 | |
| 
 | |
|             // Replace the action placeholder
 | |
|             if memstr(pattern, "/:action") {
 | |
|                 let pattern = str_replace("/:action", idPattern, pattern);
 | |
|             }
 | |
| 
 | |
|             // Replace the params placeholder
 | |
|             if memstr(pattern, "/:params") {
 | |
|                 let pattern = str_replace("/:params", "(/.*)*", pattern);
 | |
|             }
 | |
| 
 | |
|             // Replace the int placeholder
 | |
|             if memstr(pattern, "/:int") {
 | |
|                 let pattern = str_replace("/:int", "/([0-9]+)", pattern);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Check if the pattern has parantheses in order to add the regex delimiters
 | |
|         if memstr(pattern, "(") {
 | |
|             return "#^" . pattern . "$#";
 | |
|         }
 | |
| 
 | |
|         // Square brackets are also checked
 | |
|         if memstr(pattern, "[") {
 | |
|             return "#^" . pattern . "$#";
 | |
|         }
 | |
| 
 | |
|         return pattern;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set one or more HTTP methods that constraint the matching of the route
 | |
|      *
 | |
|      *<code>
 | |
|      * $route->via('GET');
 | |
|      * $route->via(array('GET', 'POST'));
 | |
|      *</code>
 | |
|      *
 | |
|      * @param string|array httpMethods
 | |
|      * @return Test\Router\Route
 | |
|      */
 | |
|     public function via(httpMethods)
 | |
|     {
 | |
|         let this->_methods = httpMethods;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Extracts parameters from a string
 | |
|      *
 | |
|      * @param string pattern
 | |
|      */
 | |
|     public function extractNamedParams(string pattern)
 | |
|     {
 | |
| 
 | |
|         char ch;
 | |
|         var tmp, matches;
 | |
|         boolean notValid = false;
 | |
|         int cursor, cursorVar, marker, bracketCount = 0, parenthesesCount = 0, foundPattern = 0;
 | |
|         int intermediate = 0, numberMatches = 0;
 | |
|         string route, item, variable, regexp;
 | |
| 
 | |
|         if strlen(pattern) <= 0 {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         let matches = [],
 | |
|         route = "";
 | |
| 
 | |
|         for cursor, ch in pattern {
 | |
| 
 | |
|             if parenthesesCount == 0 {
 | |
|                 if ch == '{' {
 | |
|                     if bracketCount == 0 {
 | |
|                         let marker = cursor + 1,
 | |
|                             intermediate = 0,
 | |
|                             notValid = false;
 | |
|                     }
 | |
|                     let bracketCount++;
 | |
|                 } else {
 | |
|                     if ch == '}' {
 | |
|                         let bracketCount--;
 | |
|                         if intermediate > 0 {
 | |
|                             if bracketCount == 0 {
 | |
| 
 | |
|                                 let numberMatches++,
 | |
|                                     variable = null,
 | |
|                                     regexp = null,
 | |
|                                     item = (string) substr(pattern, marker, cursor - marker);
 | |
| 
 | |
|                                 for cursorVar, ch in item {
 | |
| 
 | |
|                                     if ch == '\0' {
 | |
|                                         break;
 | |
|                                     }
 | |
| 
 | |
|                                     if cursorVar == 0 && !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
 | |
|                                         let notValid = true;
 | |
|                                         break;
 | |
|                                     }
 | |
| 
 | |
|                                     if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <='9') || ch == '-' || ch == '_' || ch ==  ':' {
 | |
|                                         if ch == ':' {
 | |
|                                             let variable = (string) substr(item, 0, cursorVar),
 | |
|                                                 regexp = (string) substr(item, cursorVar + 1);
 | |
|                                             break;
 | |
|                                         }
 | |
|                                     } else {
 | |
|                                         let notValid = true;
 | |
|                                         break;
 | |
|                                     }
 | |
| 
 | |
|                                 }
 | |
| 
 | |
|                                 if !notValid {
 | |
| 
 | |
|                                     let tmp = numberMatches;
 | |
| 
 | |
|                                     if variable && regexp {
 | |
| 
 | |
|                                         let foundPattern = 0;
 | |
|                                         for ch in regexp {
 | |
|                                             if ch == '\0' {
 | |
|                                                 break;
 | |
|                                             }
 | |
|                                             if !foundPattern {
 | |
|                                                 if ch == '(' {
 | |
|                                                     let foundPattern = 1;
 | |
|                                                 }
 | |
|                                             } else {
 | |
|                                                 if ch == ')' {
 | |
|                                                     let foundPattern = 2;
 | |
|                                                     break;
 | |
|                                                 }
 | |
|                                             }
 | |
|                                         }
 | |
| 
 | |
|                                         if foundPattern != 2 {
 | |
|                                             let route .= '(',
 | |
|                                                 route .= regexp,
 | |
|                                                 route .= ')';
 | |
|                                         } else {
 | |
|                                             let route .= regexp;
 | |
|                                         }
 | |
|                                         let matches[variable] = tmp;
 | |
|                                     } else {
 | |
|                                         let route .= "([^/]*)",
 | |
|                                             matches[item] = tmp;
 | |
|                                     }
 | |
|                                 } else {
 | |
|                                     let route .= "{" . item ."}";
 | |
|                                 }
 | |
|                                 continue;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if bracketCount == 0 {
 | |
|                 if ch == '(' {
 | |
|                     let parenthesesCount++;
 | |
|                 } else {
 | |
|                     if ch == ')' {
 | |
|                         let parenthesesCount--;
 | |
|                         if parenthesesCount == 0 {
 | |
|                             let numberMatches++;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if bracketCount > 0 {
 | |
|                 let intermediate++;
 | |
|             } else {
 | |
|                 let route .= ch;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return [route, matches];
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Reconfigure the route adding a new pattern and a set of paths
 | |
|      *
 | |
|      * @param string pattern
 | |
|      * @param array paths
 | |
|      */
 | |
|     public function reConfigure(pattern, paths=null)
 | |
|     {
 | |
|         var moduleName, controllerName, actionName,
 | |
|             parts, routePaths, realClassName, namespaceName,
 | |
|             pcrePattern, compiledPattern, extracted;
 | |
| 
 | |
|         if typeof pattern != "string" {
 | |
|             throw new Exception("The pattern must be string");
 | |
|         }
 | |
| 
 | |
|         if paths !== null {
 | |
|             if typeof paths == "string" {
 | |
| 
 | |
|                 let moduleName = null,
 | |
|                     controllerName = null,
 | |
|                     actionName = null;
 | |
| 
 | |
|                 // Explode the short paths using the :: separator
 | |
|                 let parts = explode("::", paths);
 | |
| 
 | |
|                 // Create the array paths dynamically
 | |
|                 switch count(parts) {
 | |
|                     case 3:
 | |
|                         let moduleName = parts[0],
 | |
|                             controllerName = parts[1],
 | |
|                             actionName = parts[2];
 | |
|                         break;
 | |
|                     case 2:
 | |
|                         let controllerName = parts[0],
 | |
|                             actionName = parts[1];
 | |
|                         break;
 | |
|                     case 1:
 | |
|                         let controllerName = parts[0];
 | |
|                         break;
 | |
|                 }
 | |
| 
 | |
|                 let routePaths = [];
 | |
| 
 | |
|                 // Process module name
 | |
|                 if moduleName !== null {
 | |
|                     let routePaths["module"] = moduleName;
 | |
|                 }
 | |
| 
 | |
|                 // Process controller name
 | |
|                 if controllerName !== null {
 | |
| 
 | |
|                     // Check if we need to obtain the namespace
 | |
|                     if memstr(controllerName, "\\") {
 | |
| 
 | |
|                         // Extract the real class name from the namespaced class
 | |
|                         let realClassName = get_class_ns(controllerName);
 | |
| 
 | |
|                         // Extract the namespace from the namespaced class
 | |
|                         let namespaceName = get_ns_class(controllerName);
 | |
| 
 | |
|                         // Update the namespace
 | |
|                         if namespaceName {
 | |
|                             let routePaths["namespace"] = namespaceName;
 | |
|                         }
 | |
|                     } else {
 | |
|                         let realClassName = controllerName;
 | |
|                     }
 | |
| 
 | |
|                     // Always pass the controller to lowercase
 | |
|                     let routePaths["controller"] = uncamelize(realClassName);
 | |
|                 }
 | |
| 
 | |
|                 // Process action name
 | |
|                 if actionName !== null {
 | |
|                     let routePaths["action"] = actionName;
 | |
|                 }
 | |
|             } else {
 | |
|                 let routePaths = paths;
 | |
|             }
 | |
|         } else {
 | |
|             let routePaths = [];
 | |
|         }
 | |
| 
 | |
|         if typeof routePaths !== "array" {
 | |
|             throw new Exception("The route contains invalid paths");
 | |
|         }
 | |
| 
 | |
|         // If the route starts with '#' we assume that it is a regular expression
 | |
|         if !starts_with(pattern, "#") {
 | |
| 
 | |
|             if memstr(pattern, "{") {
 | |
|                 // The route has named parameters so we need to extract them
 | |
|                 let extracted = this->extractNamedParams(pattern),
 | |
|                     pcrePattern = extracted[0],
 | |
|                     routePaths = array_merge(routePaths, extracted[1]);
 | |
|             } else {
 | |
|                 let pcrePattern = pattern;
 | |
|             }
 | |
| 
 | |
|             // Transform the route's pattern to a regular expression
 | |
|             let compiledPattern = this->compilePattern(pcrePattern);
 | |
|         } else {
 | |
|             let compiledPattern = pattern;
 | |
|         }
 | |
| 
 | |
|         // Update the original pattern
 | |
|         let this->_pattern = pattern;
 | |
| 
 | |
|         // Update the compiled pattern
 | |
|         let this->_compiledPattern = compiledPattern;
 | |
| 
 | |
|         //Update the route's paths
 | |
|         let this->_paths = routePaths;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the route's name
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function getName()
 | |
|     {
 | |
|         return this->_name;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the route's name
 | |
|      *
 | |
|      *<code>
 | |
|      * $router->add('/about', array(
 | |
|      *     'controller' => 'about'
 | |
|      * ))->setName('about');
 | |
|      *</code>
 | |
|      *
 | |
|      * @param string name
 | |
|      * @return Route
 | |
|      */
 | |
|     public function setName(name)
 | |
|     {
 | |
|         let this->_name = name;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets a callback that is called if the route is matched.
 | |
|      * The developer can implement any arbitrary conditions here
 | |
|      * If the callback returns false the route is treaded as not matched
 | |
|      *
 | |
|      * @param callback callback
 | |
|      * @return Test\Router\Route
 | |
|      */
 | |
|     public function beforeMatch(callback)
 | |
|     {
 | |
|         let this->_beforeMatch = callback;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the 'before match' callback if any
 | |
|      *
 | |
|      * @return mixed
 | |
|      */
 | |
|     public function getBeforeMatch()
 | |
|     {
 | |
|         return this->_beforeMatch;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the route's id
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function getRouteId()
 | |
|     {
 | |
|         return this->_id;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the route's pattern
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function getPattern()
 | |
|     {
 | |
|         return this->_pattern;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the route's compiled pattern
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function getCompiledPattern()
 | |
|     {
 | |
|         return this->_compiledPattern;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the paths
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function getPaths()
 | |
|     {
 | |
|         return this->_paths;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the paths using positions as keys and names as values
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function getReversedPaths()
 | |
|     {
 | |
|         var reversed, path, position;
 | |
| 
 | |
|         let reversed = [];
 | |
|         for path, position in this->_paths {
 | |
|             let reversed[position] = path;
 | |
|         }
 | |
|         return reversed;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets a set of HTTP methods that constraint the matching of the route (alias of via)
 | |
|      *
 | |
|      *<code>
 | |
|      * $route->setHttpMethods('GET');
 | |
|      * $route->setHttpMethods(array('GET', 'POST'));
 | |
|      *</code>
 | |
|      *
 | |
|      * @param string|array httpMethods
 | |
|      * @return Test\Router\Route
 | |
|      */
 | |
|     public function setHttpMethods(httpMethods)
 | |
|     {
 | |
|         let this->_methods = httpMethods;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the HTTP methods that constraint matching the route
 | |
|      *
 | |
|      * @return string|array
 | |
|      */
 | |
|     public function getHttpMethods()
 | |
|     {
 | |
|         return this->_methods;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets a hostname restriction to the route
 | |
|      *
 | |
|      *<code>
 | |
|      * $route->setHostname('localhost');
 | |
|      *</code>
 | |
|      *
 | |
|      * @param string|array httpMethods
 | |
|      * @return Test\Router\Route
 | |
|      */
 | |
|     public function setHostname(hostname)
 | |
|     {
 | |
|         let this->_hostname = hostname;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the hostname restriction if any
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function getHostname()
 | |
|     {
 | |
|         return this->_hostname;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds a converter to perform an additional transformation for certain parameter
 | |
|      *
 | |
|      * @param string name
 | |
|      * @param callable converter
 | |
|      * @return Test\Router\Route
 | |
|      */
 | |
|     public function convert(name, converter)
 | |
|     {
 | |
|         let this->_converters[name] = converter;
 | |
|         return this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the router converter
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function getConverters()
 | |
|     {
 | |
|         return this->_converters;
 | |
|     }
 | |
| 
 | |
| }
 |