. namespace hoplite\http2; /*! A RouteMap parses a HTTP Request URL, matches it against its set of rules, and finds one that matches. If it is successful, data are extracted from the URL, based on the specified pattern. The map is an associative array of URL prefix patterns to a result value. When used with the Router Middleware, the value should be a Middleware that can be built by Pipeline::buildMiddleware(). An Action subclass typical. The keys can be either a URL prefix or a regular expression. For URL prefixes, the pattern is matched relative to the Request::url field. Patterns should not begin with a slash. Path fragment parameter extraction can be performed as well. For example, the pattern 'user/view/{id}' will match a URL like http://example.com/webapp/user/view/42 And the match result data will have a key called 'id' with value 42. Typically prefix matching will be done for the above patterns. This may be unwanted if a more general path needs to execute before a more descendent one. Using strict matching, invoked by ending a pattern with two slashes, pattern matching becomes exact in that all path components must match. Regular expression matching is not limited to prefix patterns and can match any part of the URL (though prefix matching can be enforced using the the standard regex '^' character). Regex patterns must begin and end with '/' characters. During evaluation, if any pattern groups are matched, the resulting matches will be placed in Request data via the 'url_pattern' key. The following will match the same URL as above: '/^user\/view\/([0-9]+)/' */ class RouteMap { /*! @var array The map of URLs to actions. */ private $map; public function __construct(array $map) { $this->map = $map; } /*! @brief Evalutes the URL map and finds a match. This will take the incoming URL from the request and will match it against the patterns in the internal map. Matching occurs in a linear scan of the URL map, so precedence can be enforced by ordering rules appropriately. Regular expressions are matched and URL parameter extraction occurs each time a different rule is evaluated. If a match is made, an associative array with this stucture is returned: [ 'result' => , 'data' => [ ], 'regexp' => ] @return array|NULL A matched value in the ::map or NULL if no match. */ public function match(Request $request) { $fragments = explode('/', $request->url); $path_length = strlen($request->url); $result = []; foreach ($this->map as $rule => $action) { // First check if this is the empty rule. if (empty($rule)) { if (empty($request->url)) return [ 'result' => $action ]; else continue; } // Check if this is a regular expression rule and match it. if ($rule[0] == '/' && substr($rule, -1) == '/') { $matches = array(); if (preg_match($rule, $request->url, $matches)) { // This pattern matched, so fill out the request and return. return [ 'result' => $action, 'regexp' => $matches, ]; } } // Otherwise, this is just a normal string match. else { // Patterns that end with two slashes are exact. $is_strict = substr($rule, -2) == '//'; if ($is_strict) $rule = substr($rule, 0, -2); // Set up some variables for the loop. $is_match = TRUE; $rule_fragments = explode('/', $rule); $count_rule_fragments = count($rule_fragments); $count_fragments = count($fragments); $extractions = array(); // If this is a strict matcher, then do a quick test based on fragments. if ($is_strict && $count_rule_fragments != $count_fragments) continue; // Loop over the pieces of the rule, matching the fragments to that of // the request. foreach ($rule_fragments as $i => $rule_frag) { // Don't iterate past the length of the request. Prefix matching means // that this can still be a match. if ($i >= $count_fragments) break; // If this fragment is a key to be extracted, do so into a temporary // array. if (strlen($rule_frag) && $rule_frag[0] == '{' && substr($rule_frag, -1) == '}') { $key = substr($rule_frag, 1, -1); $extractions[$key] = $fragments[$i]; } // Otherwise, the path components mutch match. else if ($rule_frag != $fragments[$i]) { $is_match = FALSE; break; } } // If no match was made, try the next rule. if (!$is_match) continue; // A match was made, so merge the path components that were extracted by // key and return the match. return [ 'result' => $action, 'data' => $extractions, ]; } } return NULL; } }