doctrine/orm · error · RuntimeException

No alias was set before invoking add().

Error message

No alias was set before invoking add().

What it means

add('join', $parts) expects an array keyed by the alias the joins belong to; numeric keys are deprecated and get remapped to the builder's first root alias. That remapping throws RuntimeException when the builder has no root aliases at all, because there is no alias to attribute the joins to.

Source

Thrown at src/QueryBuilder.php:653

        if (is_array($dqlPart) && $dqlPartName !== 'join') {
            $dqlPart = reset($dqlPart);
        }

        if ($dqlPartName === 'join') {
            $newDqlPart = [];

            foreach ($dqlPart as $k => $v) {
                if (is_numeric($k)) {
                    Deprecation::trigger(
                        'doctrine/orm',
                        'https://github.com/doctrine/orm/pull/12051',
                        'Using numeric keys in %s for join parts is deprecated and will not be supported in 4.0. Use an associative array with the root alias as key instead.',
                        __METHOD__,
                    );
                    $aliases = $this->getRootAliases();

                    if (! isset($aliases[0])) {
                        throw new RuntimeException('No alias was set before invoking add().');
                    }

                    $k = $aliases[0];
                }

                $newDqlPart[$k] = $v;
            }

            $dqlPart = $newDqlPart;
        }

        if ($append && $isMultiple) {
            if (is_array($dqlPart)) {
                $key = key($dqlPart);

                $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
            } else {
                $this->dqlParts[$dqlPartName][] = $dqlPart;

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Pass the associative form keyed by the root alias: $qb->add('join', ['u' => $joinExpr]).
  2. Call from(User::class, 'u') before add('join', ...) so even the deprecated numeric form has a root to map to.
  3. When copying parts between builders, copy 'from' first, then 'join'.

Example fix

// before
$target->add('join', [$sourceJoinExpr]); // numeric key + no root alias -> RuntimeException

// after
$target->from(User::class, 'u');
$target->add('join', ['u' => $sourceJoinExpr]);
Defensive patterns

Strategy: validation

Validate before calling

if ($qb->getRootAliases() === []) {
    throw new LogicException("Add from() before add('join', ...): joins need a root alias.");
}
$qb->add('join', ['u' => $joinExpr]); // associative key avoids the deprecated numeric path

Try / catch

Catch \Doctrine\ORM\RuntimeException in generic part-copy code and rethrow, including both builders' root aliases in the message.

Prevention

When it happens

Trigger: $qb->add('join', [$joinExpr]) (numeric list) on a builder with no from() yet; copying one builder's join parts into a fresh builder whose root has not been added; numeric-string keys like '0' also match the is_numeric remap path and emit the deprecation notice.

Common situations: Builder merge/clone utilities moving getDQLPart('join') arrays between builders; code written against ORM 2's numeric join arrays; filter bundles appending joins before the base query assembles its from part.

Related errors


AI-assisted analysis of doctrine/orm@d9b9ff7301 (2026-08-21). Data as JSON: /api/errors/80c224c29dbb88eb. Report an issue: GitHub.