Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. CompiledUrlMatcherDumper.php

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\Dumper

Code

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

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal