/** * Process the supplied url. * * This route takes a simplified series of patterns such as :controller and * replaces them with more complex regular expressions which are then used * within a preg_match_callback to match against the given uri. * * If a 'filter' regex is set within the $options array that regex will be * used within the preg_match_callback. Otherwise a default regex of * ([a-zA-Z0-9_\+\-%]+) is used. * * If one of the 'simplified' patterns within the rule is :results, this is * treated specially and uses a ([a-zA-Z0-9_\+\-%\/]+) regex which will match * the same as the default as well as / . * * This causes the pattern to match entire sections of uri's. Allowing a * simple pattern like the default /:controller/:action/:results to match * uri's like /foo/bar/a/b/c/d/e/f/g/h and cause everything after /foo/bar * to be added to the Payload object as results (which are in turn transformed * into key => value pairs). * * TODO: A lot of this fluffing around could (and likely should) be moved into * the __construct and other protected internal methods. * * @param Proem\Http\Request $request */ public function process(Request $request) { // All routes must at least specify a rule. if (!isset($this->options['rule'])) { return false; } // Setup. $rule = str_replace('/', '/?', $this->options['rule']); $targets = isset($this->options['targets']) ? $this->options['targets'] : []; $customFilters = isset($this->options['filters']) ? $this->options['filters'] : []; $defaultTokens = $this->defaultTokens; $defaultFilters = $this->defaultFilters; $url = $request->getRequestUri(); $tokens = []; $values = []; $results = []; // Build the main regular expression. $regex = '^' . preg_replace_callback('@:[\\w]+@', function ($matches) use($customFilters, $defaultTokens, $defaultFilters) { $key = str_replace(':', '', $matches[0]); if (isset($customFilters[$key])) { if (isset($defaultFilters[$customFilters[$key]])) { return '(' . $defaultFilters[$customFilters[$key]] . ')'; } else { if ($customFilters[$key][0] == ':') { throw new \RuntimeException("The custom filter named \"{$key}\" references a\n non-existent builtin filter named \"{$customFilters[$key]}\"."); } else { return '(' . $customFilters[$key] . ')'; } } } elseif (isset($defaultTokens[$key])) { return '(' . $defaultTokens[$key] . ')'; } else { return '(' . $defaultFilters[':default'] . ')'; } }, $rule) . '/?$'; // Find all tokens. preg_match_all('@:([\\w]+)@', $rule, $tokens, PREG_PATTERN_ORDER); $tokens = $tokens[0]; // Test the main regular expression against the url. if (preg_match('@^' . $regex . '$@', $url, $values)) { // Discard *all* matches index. array_shift($values); // Match tokens to values foreach ($tokens as $index => $value) { if (isset($values[$index])) { $results[substr($value, 1)] = urldecode($values[$index]); } } // Replace any results with specific targets.. foreach ($targets as $key => $value) { $results[$key] = $value; } // If the current $key is "params" & the string within $value looks // like a / seperated string, parse it into an associative array. if (isset($results['params']) && strpos($results['params'], '/') !== false) { preg_match_all("/([^\\/]+)\\/([^\\/]+)/", $results['params'], $m); $results = array_merge($results, array_combine($m[1], $m[2])); unset($results['params']); } // Route has matched, return results. return $results; } return false; }
public function testOptionalSwitchMatchesCenter() { $request_with = Request::create('/foo/bar/bob'); $request_without = Request::create('/foo/bob'); $route = new Route(['rule' => '/:module/:controller?/:action']); $this->assertTrue(is_array($route->process($request_with))); $this->assertTrue(is_array($route->process($request_without))); }