function CompiledUrlMatcherDumper::compileDynamicRoutes
Compiles a regular expression followed by a switch statement to match dynamic routes.
The regular expression matches both the host and the pathinfo at the same time. For stellar performance, it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible.
Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23). This name is used to "switch" to the additional logic required to match the final route.
Condition-less paths are put in a static array in the switch's default, with generic matching logic. Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases.
Last but not least:
- Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated.
- The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match. To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur.
1 call to CompiledUrlMatcherDumper::compileDynamicRoutes()
- CompiledUrlMatcherDumper::getCompiledRoutes in vendor/
symfony/ routing/ Matcher/ Dumper/ CompiledUrlMatcherDumper.php - Generates the arrays for CompiledUrlMatcher's constructor.
File
-
vendor/
symfony/ routing/ Matcher/ Dumper/ CompiledUrlMatcherDumper.php, line 247
Class
- CompiledUrlMatcherDumper
- CompiledUrlMatcherDumper creates PHP arrays to be used with CompiledUrlMatcher.
Namespace
Symfony\Component\Routing\Matcher\DumperCode
private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions) : array {
if (!$collection->all()) {
return [
[],
[],
'',
];
}
$regexpList = [];
$code = '';
$state = (object) [
'regexMark' => 0,
'regex' => [],
'routes' => [],
'mark' => 0,
'markTail' => 0,
'hostVars' => [],
'vars' => [],
];
$state->getVars = static function ($m) use ($state) {
if ('_route' === $m[1]) {
return '?:';
}
$state->vars[] = $m[1];
return '';
};
$chunkSize = 0;
$prev = null;
$perModifiers = [];
foreach ($collection->all() as $name => $route) {
preg_match('#[a-zA-Z]*$#', $route->compile()
->getRegex(), $rx);
if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()
->getPathVariables()) {
$chunkSize = 1;
$routes = new RouteCollection();
$perModifiers[] = [
$rx[0],
$routes,
];
$prev = $rx[0];
}
$routes->add($name, $route);
}
foreach ($perModifiers as [
$modifiers,
$routes,
]) {
$prev = false;
$perHost = [];
foreach ($routes->all() as $name => $route) {
$regex = $route->compile()
->getHostRegex();
if ($prev !== $regex) {
$routes = new RouteCollection();
$perHost[] = [
$regex,
$routes,
];
$prev = $regex;
}
$routes->add($name, $route);
}
$prev = false;
$rx = '{^(?';
$code .= "\n {$state->mark} => " . self::export($rx);
$startingMark = $state->mark;
$state->mark += \strlen($rx);
$state->regex = $rx;
foreach ($perHost as [
$hostRegex,
$routes,
]) {
if ($matchHost) {
if ($hostRegex) {
preg_match('#^.\\^(.*)\\$.[a-zA-Z]*$#', $hostRegex, $rx);
$state->vars = [];
$hostRegex = '(?i:' . preg_replace_callback('#\\?P<([^>]++)>#', $state->getVars, $rx[1]) . ')\\.';
$state->hostVars = $state->vars;
}
else {
$hostRegex = '(?:(?:[^./]*+\\.)++)';
$state->hostVars = [];
}
$state->mark += \strlen($rx = ($prev ? ')' : '') . "|{$hostRegex}(?");
$code .= "\n ." . self::export($rx);
$state->regex .= $rx;
$prev = true;
}
$tree = new StaticPrefixCollection();
foreach ($routes->all() as $name => $route) {
preg_match('#^.\\^(.*)\\$.[a-zA-Z]*$#', $route->compile()
->getRegex(), $rx);
$state->vars = [];
$regex = preg_replace_callback('#\\?P<([^>]++)>#', $state->getVars, $rx[1]);
if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) {
$regex = substr($regex, 0, -1);
}
$hasTrailingVar = (bool) preg_match('#\\{[\\w\\x80-\\xFF]+\\}/?$#', $route->getPath());
$tree->addRoute($regex, [
$name,
$regex,
$state->vars,
$route,
$hasTrailingSlash,
$hasTrailingVar,
]);
}
$code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions);
}
if ($matchHost) {
$code .= "\n .')'";
$state->regex .= ')';
}
$rx = ")/?\$}{$modifiers}";
$code .= "\n .'{$rx}',";
$state->regex .= $rx;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(fn($type, $message) => throw str_contains($message, $this->signalingException
->getMessage()) ? $this->signalingException : new \ErrorException($message));
try {
preg_match($state->regex, '');
} finally {
restore_error_handler();
}
$regexpList[$startingMark] = $state->regex;
}
$state->routes[$state->mark][] = [
null,
null,
null,
null,
false,
false,
0,
];
unset($state->getVars);
return [
$regexpList,
$state->routes,
$code,
];
}