symfony/routing · error · DomainException

Variable name " " cannot start with a digit in route…

Error message

Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.

What it means

Route variable names extracted from {placeholders} must start with a non-digit: PCRE subpattern names and PHP variables cannot begin with a digit, so the value could never be used as a controller action argument. compilePattern throws a DomainException when the variable name matches /^\d/.

Solutions

  1. Rename the variable to start with a letter or underscore, e.g. {2fa_code} → {twoFactorCode} or {_2fa_code}.
  2. Pass the numeric part as a fixed prefix or default instead of a variable, e.g. /blog/2fa/{code}.
  3. Search route definitions with a regex like \{\d for offending placeholders.

Example fix

// before
$route = new Route('/login/{2fa_code}');

// after
$route = new Route('/login/{twoFactorCode}');
Defensive patterns

Strategy: validation

Validate before calling

preg_match_all('/\{(!)?([^}]+)\}/', $route->getPath(), $m);
foreach ($m[2] as $var) {
    if (preg_match('/^\d/', $var)) {
        throw new \InvalidArgumentException("Variable '$var' must not start with a digit.");
    }
}

Try / catch

try {
    (new RouteCompiler())->compile($route);
} catch (\DomainException $e) {
    // rename the offending variable and recompile
}

Prevention

When it happens

Trigger: Compiling a route with a placeholder like /blog/{2fa_code}, /items/{0id}, or any {<digit>...} variable name.

Common situations: Placeholders meant to denote numbered items ({1st}, {2fa}); generated route names from data with numeric keys; typos where a digit replaced a letter.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of symfony/routing@83fa223250 (2026-09-14). Data as JSON: /api/errors/56638764df803d66. Report an issue: GitHub.

Appendix: source

Thrown at RouteCompiler.php:147

            $varName = $match[2][0];
            // get all static text preceding the current variable
            $precedingText = substr($pattern, $pos, $match[0][1] - $pos);
            $pos = $match[0][1] + \strlen($match[0][0]);

            if (!\strlen($precedingText)) {
                $precedingChar = '';
            } elseif ($useUtf8) {
                preg_match('/.$/u', $precedingText, $precedingChar);
                $precedingChar = $precedingChar[0];
            } else {
                $precedingChar = substr($precedingText, -1);
            }
            $isSeparator = '' !== $precedingChar && str_contains(static::SEPARATORS, $precedingChar);

            // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the
            // variable would not be usable as a Controller action argument.
            if (preg_match('/^\d/', $varName)) {
                throw new \DomainException(\sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern));
            }
            if (\in_array($varName, $variables)) {
                throw new \LogicException(\sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName));
            }

            if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
                throw new \DomainException(\sprintf('Variable name "%s" cannot be longer than %d characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
            }

            if ($isSeparator && $precedingText !== $precedingChar) {
                $tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))];
            } elseif (!$isSeparator && '' !== $precedingText) {
                $tokens[] = ['text', $precedingText];
            }

            $regexp = $route->getRequirement($varName);
            if (null === $regexp) {
                $followingPattern = substr($pattern, $pos);

View on GitHub (pinned to 83fa223250)