doctrine/orm · error · RuntimeException

Unknown composite {type}

Error message

Unknown composite {type}

What it means

When a Criteria containing a CompositeExpression filters a persistent collection (matching()) or is walked into SQL, SqlExpressionVisitor::walkCompositeExpression() renders the expression by matching on the doctrine/collections type constants TYPE_AND, TYPE_OR and TYPE_NOT. Any other type string hits the default arm and throws RuntimeException. Modern doctrine/collections validates types at construction, so this means a hand-built/subclassed CompositeExpression or a mismatched collections version.

Source

Thrown at src/Persisters/SqlExpressionVisitor.php:68

    /**
     * Converts a composite expression into the target query language output.
     *
     * @throws RuntimeException
     */
    public function walkCompositeExpression(CompositeExpression $expr): string
    {
        $expressionList = [];

        foreach ($expr->getExpressionList() as $child) {
            $expressionList[] = $this->dispatch($child);
        }

        return match ($expr->getType()) {
            CompositeExpression::TYPE_AND => '(' . implode(' AND ', $expressionList) . ')',
            CompositeExpression::TYPE_OR => '(' . implode(' OR ', $expressionList) . ')',
            CompositeExpression::TYPE_NOT => 'NOT (' . $expressionList[0] . ')',
            default => throw new RuntimeException('Unknown composite ' . $expr->getType()),
        };
    }

    /**
     * Converts a value expression into the target query language part.
     */
    public function walkValue(Value $value): string
    {
        return '?';
    }
}

View on GitHub (pinned to d9b9ff7301)

Solutions

  1. Use the factories: Criteria::expr()->andX(...), ->orX(...), ->notX(...) — they only produce valid types
  2. Update doctrine/collections to a current version that validates types in the constructor
  3. Never construct CompositeExpression with literal type strings

Example fix

// before
$criteria->andWhere(new CompositeExpression('XOR', [$expr1, $expr2]));

// after
$criteria->andWhere(Criteria::expr()->orX(
    Criteria::expr()->andX($expr1, $expr2),
    Criteria::expr()->andX($expr3)
));
Defensive patterns

Strategy: type-guard

Validate before calling

if (! in_array($expr->getType(), [CompositeExpression::TYPE_AND, CompositeExpression::TYPE_OR, CompositeExpression::TYPE_NOT], true)) { throw new InvalidArgumentException('Bad composite type'); }

Type guard

/** @param mixed $expr */
function isSupportedComposite(mixed $expr): bool
{
    return $expr instanceof CompositeExpression
        && in_array($expr->getType(), [CompositeExpression::TYPE_AND, CompositeExpression::TYPE_OR, CompositeExpression::TYPE_NOT], true);
}

Prevention

When it happens

Trigger: new CompositeExpression('XOR', [...]) or a subclass injecting a custom type, then Criteria->andWhere()/orWhere() passed to PersistentCollection::matching()/EntityRepository::matching(); using an outdated doctrine/collections that permits arbitrary type strings.

Common situations: Custom expression classes extending doctrine/collections' CompositeExpression; pinning an old doctrine/collections version alongside newer doctrine/orm.

Related errors


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