symfony/routing · error · InvalidArgumentException

A placeholder name must be a string

Error message

A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?

What it means

AttributeClassLoader::addRoute validates that every route requirement key is a placeholder string. When an attribute requirement is passed as a list (e.g. #[Route(..., requirements: ['\\d+'])]) the key is an integer, so the loader throws InvalidArgumentException naming the route, class and method.

Solutions

  1. Give each requirement an explicit string key matching the route placeholder: requirements: ['id' => '\d+']
  2. Verify every requirements entry has a named key on the #[Route]/#[RouteCollision...] attribute
  3. Check the class-level #[Route] attribute globals too, since requirements are merged with globals

Example fix

// before
#[Route('/blog/{id}', requirements: ['\d+'])]
// after
#[Route('/blog/{id}', requirements: ['id' => '\d+'])]
Defensive patterns

Strategy: validation

Validate before calling

foreach ($attr->requirements as $k => $v) { if (is_int($k)) throw new \InvalidArgumentException("Requirement for placeholder '$v' must use a named key"); }

Type guard

function hasStringRequirementKeys(array $requirements): bool { foreach ($requirements as $k => $v) { if (!is_string($k)) return false; } return true; }

Prevention

When it happens

Trigger: Declaring an attribute route with a requirements array that uses implicit integer keys, e.g. requirements: ['/d+'] instead of ['id' => '/d+'], so the requirement value has no placeholder name to match against.

Common situations: Migrating from YAML/XML where requirements could be positional; typos like requirements: ['\d+']; refactoring that dropped the placeholder key; copying a route definition and losing the named key.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at Loader/AttributeClassLoader.php:158

    }

    /**
     * @param RouteAttribute $attr or an object that exposes a similar interface
     */
    protected function addRoute(RouteCollection $collection, object $attr, array $globals, \ReflectionClass $class, \ReflectionMethod $method): void
    {
        if ($attr->envs && !\in_array($this->env, $attr->envs, true)) {
            return;
        }

        $name = $attr->name ?? $this->getDefaultRouteName($class, $method);
        $name = $globals['name'].$name;

        $requirements = $attr->requirements;

        foreach ($requirements as $placeholder => $requirement) {
            if (\is_int($placeholder)) {
                throw new \InvalidArgumentException(\sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName()));
            }
        }

        $defaults = array_replace($globals['defaults'], $attr->defaults);
        $requirements = array_replace($globals['requirements'], $requirements);
        $options = array_replace($globals['options'], $attr->options);
        $schemes = array_unique(array_merge($globals['schemes'], $attr->schemes));
        $methods = array_unique(array_merge($globals['methods'], $attr->methods));

        $host = $attr->host ?? $globals['host'];
        $condition = $attr->condition ?? $globals['condition'];
        $priority = $attr->priority ?? $globals['priority'];

        $path = $attr->path;
        $prefix = $globals['localized_paths'] ?: $globals['path'];
        $paths = [];

        if (\is_array($path)) {

View on GitHub (pinned to 83fa223250)