{"id":"fba3fddb09f1eccf","repo":"laravel/framework","slug":"a-subquery-must-be-a-query-builder-instance-a-clo","errorCode":null,"errorMessage":"A subquery must be a query builder instance, a Closure, or a string.","messagePattern":"A subquery must be a query builder instance, a Closure, or a string\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Illuminate/Database/Query/Builder.php","lineNumber":436,"sourceCode":"\n    /**\n     * Parse the subquery into SQL and bindings.\n     *\n     * @param  mixed  $query\n     * @return array\n     *\n     * @throws \\InvalidArgumentException\n     */\n    protected function parseSub($query)\n    {\n        if ($query instanceof self || $query instanceof EloquentBuilder || $query instanceof Relation) {\n            $query = $this->prependDatabaseNameIfCrossDatabaseQuery($query);\n\n            return [$query->toSql(), $query->getBindings()];\n        } elseif (is_string($query)) {\n            return [$query, []];\n        } else {\n            throw new InvalidArgumentException(\n                'A subquery must be a query builder instance, a Closure, or a string.'\n            );\n        }\n    }\n\n    /**\n     * Prepend the database name if the given query is on another database.\n     *\n     * @param  mixed  $query\n     * @return mixed\n     */\n    protected function prependDatabaseNameIfCrossDatabaseQuery($query)\n    {\n        if ($query->getConnection()->getDatabaseName() !==\n            $this->getConnection()->getDatabaseName()) {\n            $databaseName = $query->getConnection()->getDatabaseName();\n\n            if (! str_starts_with($query->from, $databaseName) && ! str_contains($query->from, '.')) {","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/laravel/framework/blob/bd6b5437e6ad87bb49f9b426724f07a9f64e9683/src/Illuminate/Database/Query/Builder.php#L418-L454","documentation":"Thrown by Query\\Builder::parseSub when a value passed to a subquery-accepting API is neither a Query\\Builder, an Eloquent\\Builder, a Relation, nor a string. parseSub is invoked (via createSub) by methods like whereExists, joinSub, fromSub, orderBy subquery, and groupBy subquery. Despite the message naming Closure, Closures are resolved earlier in createSub; reaching this branch means the caller passed an array, null, int, object, or resource where a query was expected.","triggerScenarios":"Passing a raw array to `whereExists([...])` instead of a closure/builder. Calling `orderBySub(['col'])`. Handing `null` to `fromSub()` after an optional-relation lookup returns null. Passing an Eloquent Collection instead of a query builder. Passing a model instance rather than `Model::query()`.","commonSituations":"Refactoring a `whereIn` to a `whereExists` and forgetting the closure wrapper; nullable relation resolution feeding directly into a subquery API; dynamic data (e.g. request input) reaching a subquery parameter without coercion to a builder or SQL string.","solutions":["Wrap the argument in a closure: `whereExists(fn ($q) => $q->select(...)->from(...))`.","Pass the query builder instance directly: `User::query()->where('active', 1)` instead of `User::all()`.","If you intend raw SQL, pass a string: `fromSub('select ...', 'sub')`.","Guard nullable relations: `$relation ?->getQuery() ?? User::query()->whereRaw('1=0')`."],"exampleFix":"// before\nUser::whereExists([$someIds])->get();\n// => A subquery must be a query builder instance, a Closure, or a string.\n\n// after\nUser::whereExists(function ($q) use ($someIds) {\n    $q->select(DB::raw(1))->from('orders')->whereIn('user_id', $someIds);\n})->get();","handlingStrategy":"type-guard","validationCode":"if (! is_string($arg)\n    && ! $arg instanceof \\Illuminate\\Database\\Query\\Builder\n    && ! $arg instanceof \\Illuminate\\Database\\Eloquent\\Builder\n    && ! $arg instanceof \\Illuminate\\Database\\Eloquent\\Relations\\Relation\n    && ! $arg instanceof \\Closure) {\n    throw new InvalidArgumentException('Subquery argument must be a builder, relation, closure, or SQL string.');\n}","typeGuard":"use Illuminate\\Database\\Query\\Builder as QB;\nuse Illuminate\\Database\\Eloquent\\Builder as EB;\nuse Illuminate\\Database\\Eloquent\\Relations\\Relation;\n\nfunction isSubqueryable(mixed $q): bool\n{\n    return $q instanceof QB\n        || $q instanceof EB\n        || $q instanceof Relation\n        || $q instanceof \\Closure\n        || is_string($q);\n}","tryCatchPattern":"try {\n    $query->whereExists($maybeSub);\n} catch (\\InvalidArgumentException $e) {\n    if (str_contains($e->getMessage(), 'subquery must be a query builder')) {\n        // fall back to a no-op subquery or rebuild with a closure\n    }\n    throw $e;\n}","preventionTips":["Always wrap subquery SQL in a closure passed to whereExists/whereHas unless you hold an explicit builder.","Null-check optional relations before feeding them to subquery APIs.","Use static analysis (PHPStan) with the builder generics to catch wrong types at CI time."],"tags":["query-builder","subquery","invalid-argument"],"analyzedSha":"bd6b5437e6ad87bb49f9b426724f07a9f64e9683","analyzedAt":"2026-08-06T00:28:32.783Z","schemaVersion":2}