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;
 | 
						|
    }
 | 
						|
 | 
						|
}
 |