ice framework documentation ice doc v1.10.1
Class Ice Mvc

Route

    
namespace Ice\Mvc;

use Ice\Di;
use Ice\Exception;

/**
 * This class represents every route added to the router.
 *
 * @package     Ice/Router
 * @category    Component
 * @author      Ice Team
 * @copyright   (c) 2014-2023 Ice Team
 * @license     http://iceframework.org/license
 */
class Route
{
    // Route URI string
    protected routeUri = "";

    // Regular expressions for route keys
    protected regexMap = [];

    // Compiled regex cache
    protected routeRegex;

    // Regular expressions for route keys
    protected defaults = ["action": "index"] { get, set };

    protected method { get };
    protected error { get };

    // Defines the pattern of a {placeholder}
    const REGEX_KEYWORD = "\{([a-zA-Z0-9_]++)\}";

    // What can be part of a {placeholder} value
    const REGEX_PLACEHOLDER = "[^/.,;?\n]++";

    // What must be escaped in the route regex
    const REGEX_ESCAPE = "[.\\+*?^$=!|]";

    /**
     * Constructs a route. Each {key} will be translated to a regular expression
     * using a default regular expression pattern. You can override the default pattern
     * by providing a pattern for the key:
     *
     * 

     *     // This route will only match when {id} is a digit
     *     new Route("/blog/{action}/{id}", ["id" => "\d+"], ['GET', 'POST']);
     *
     *     // This route will match when {path} is anything
     *     new Route("/{path}", ["path" => ".*"]);
     * 
* * It is also possible to create optional segments by using parentheses in * the URI definition: * *

     *     // This is the standard default route, and no keys are required
     *     new Route('/{module}[/{controller}[/{action}[/{id}[/params]]]]');
     *
     *     // This route only requires the {file} key
     *     new Route('[/{path}/]{file}[.{ext}]', ['path' => '.*', 'ext' => '\w+']);
     * 
* *

     *     $route = new Route($uri, $regex, ['GET', 'POST']);
     * 
* * @param string uri Route URI pattern * @param array regexMap Key patterns map * @param mix method Request method limitation, * for no limit or an array of methods */ public function __construct(string uri = null, array! regexMap = null, var method = "*") { var regex, search, replace, key, value; if uri === null { // Assume the route is from cache return; } // Store the URI that this route will match let this->routeUri = uri; if !empty regexMap { let this->regexMap = regexMap; } if empty method { let this->method = "*"; } else { if typeof method == "array" { let this->method = array_map("strtoupper", method); } else { let this->method = strtoupper(method); } } // The URI should be considered literal except for keys and optional parts // Escape everything preg_quote would escape except for : [ ] { } let regex = preg_replace("#" . self::REGEX_ESCAPE . "#", "\\\\$0", this->routeUri); if strpos(regex, "[") !== false { // Make optional parts of the URI non-capturing and optional let regex = str_replace(["[", "]"], ["(?:", ")?"], regex); } // Insert default regex for keys let regex = str_replace(["{", "}"], ["(?P<", ">" . self::REGEX_PLACEHOLDER . ")"], regex); if !empty this->regexMap { let search = [], replace = []; for key, value in this->regexMap { let search[] = "<" . key . ">" . self::REGEX_PLACEHOLDER, replace[] = "<" . key . ">" . value; } // Replace the default regex with the user-specified regex let regex = str_replace(search, replace, regex); } // Store the compiled regex locally let this->routeRegex = "#^" . regex . "$#uD"; } /** * Tests if the route matches a given URI and method. * *

     *     // Params: controller = blog, action = edit, id = 10
     *     $params = route->matches("/blog/edit/10");
     * 
* * @param string URI to match * @param string method * @return array|false|null Routed parameters, method not allowed or no match */ public function matches(string uri, string method = "*") { var params, key, value, matches = []; if !this->checkMethod(method) { return false; } if !preg_match(this->routeRegex, uri, matches) { // NOT FOUND return null; } let params = this->defaults; for key, value in matches { if is_int(key) || value === "" { continue; } let params[key] = value; } return params; } /** * Tests if the route allows a given method. * * @param string method * @return boolean */ public function checkMethod(string method) { if this->method != "*" && method != "*" { if !empty method { let method = strtoupper(method); } // For HEAD requests, attempt fallback to GET if method === "HEAD" { let method = "GET"; } if typeof this->method == "string" && method != this->method || typeof this->method == "array" && !in_array(method, this->method) { // METHOD NOT ALLOWED return false; } } return true; } /** * Generates a URI for the current route based on the parameters given. (AKA. reverse route) * *

     *     // Using the "default" route: /blog/post/10
     *     $uri = $route->uri(["controller" => "blog", "action" => "post", "id" => 10]);
     *     if (!$uri) echo $route->getError();
     * 
* * @param array URI parameters * @return string|false */ public function uri(array! params = null) { var defaults, uri, param, search, key, replace, matches = []; let uri = this->routeUri, defaults = this->defaults; if strpos(uri, "{") === false && strpos(uri, "[") === false { // This is a static route, no need to replace anything return uri; } if !empty params { let defaults = array_merge(defaults, params); } while preg_match("#\[[^[\]]++\]#", uri, matches) { // Search for the matched value let search = matches[0]; // Remove the parenthesis from the match as the replace let replace = substr(search, 1, -1); while preg_match("#" . self::REGEX_KEYWORD . "#", replace, matches) { let key = matches[0], param = matches[1]; if isset defaults[param] { // Replace the key with the parameter value let replace = str_replace(key, defaults[param], replace); } else { // This group has missing parameters let replace = ""; break; } } // Replace the group in the URI let uri = str_replace(search, replace, uri); } while preg_match("#" . self::REGEX_KEYWORD . "#", uri, matches) { let key = matches[0], param = matches[1]; if !isset defaults[param] { // Ungrouped parameters are required let this->error = "Required route parameter not passed: " . param; return false; } let uri = str_replace(key, defaults[param], uri); } // Trim all extra slashes from the URI return preg_replace("#//+#", "/", rtrim(uri, "/")); } }