{"record":{"id":"28a5cd12c2d5252f","repo":"doctrine/orm","slug":"unknown-composite-type","errorCode":null,"errorMessage":"Unknown composite {type}","messagePattern":"Unknown composite (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Persisters/SqlExpressionVisitor.php","lineNumber":68,"sourceCode":"\n    /**\n     * Converts a composite expression into the target query language output.\n     *\n     * @throws RuntimeException\n     */\n    public function walkCompositeExpression(CompositeExpression $expr): string\n    {\n        $expressionList = [];\n\n        foreach ($expr->getExpressionList() as $child) {\n            $expressionList[] = $this->dispatch($child);\n        }\n\n        return match ($expr->getType()) {\n            CompositeExpression::TYPE_AND => '(' . implode(' AND ', $expressionList) . ')',\n            CompositeExpression::TYPE_OR => '(' . implode(' OR ', $expressionList) . ')',\n            CompositeExpression::TYPE_NOT => 'NOT (' . $expressionList[0] . ')',\n            default => throw new RuntimeException('Unknown composite ' . $expr->getType()),\n        };\n    }\n\n    /**\n     * Converts a value expression into the target query language part.\n     */\n    public function walkValue(Value $value): string\n    {\n        return '?';\n    }\n}\n","sourceCodeStart":50,"sourceCodeEnd":80,"githubUrl":"https://github.com/doctrine/orm/blob/d9b9ff73016bf598ae07515f97289ce8074e97a5/src/Persisters/SqlExpressionVisitor.php#L50-L80","documentation":"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.","triggerScenarios":"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.","commonSituations":"Custom expression classes extending doctrine/collections' CompositeExpression; pinning an old doctrine/collections version alongside newer doctrine/orm.","solutions":["Use the factories: Criteria::expr()->andX(...), ->orX(...), ->notX(...) — they only produce valid types","Update doctrine/collections to a current version that validates types in the constructor","Never construct CompositeExpression with literal type strings"],"exampleFix":"// before\n$criteria->andWhere(new CompositeExpression('XOR', [$expr1, $expr2]));\n\n// after\n$criteria->andWhere(Criteria::expr()->orX(\n    Criteria::expr()->andX($expr1, $expr2),\n    Criteria::expr()->andX($expr3)\n));","handlingStrategy":"type-guard","validationCode":"if (! in_array($expr->getType(), [CompositeExpression::TYPE_AND, CompositeExpression::TYPE_OR, CompositeExpression::TYPE_NOT], true)) { throw new InvalidArgumentException('Bad composite type'); }","typeGuard":"/** @param mixed $expr */\nfunction isSupportedComposite(mixed $expr): bool\n{\n    return $expr instanceof CompositeExpression\n        && in_array($expr->getType(), [CompositeExpression::TYPE_AND, CompositeExpression::TYPE_OR, CompositeExpression::TYPE_NOT], true);\n}","tryCatchPattern":null,"preventionTips":["Build composites only via Criteria::expr() factories","Keep doctrine/collections current","Never pass raw type strings to CompositeExpression"],"tags":["criteria","composite-expression","doctrine-collections","matching"],"backgroundTag":"invalid-criteria-expression","analyzedSha":"d9b9ff73016bf598ae07515f97289ce8074e97a5","analyzedAt":"2026-08-21T06:13:15.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}