
Laravel Scout 的 Builder 不支持 union(),需通过自定义查询、集合合并或引擎扩展(如 Algolia Scout Extended)来实现多模型混合搜索与分页。本文详解三种兼容主流驱动的生产级解决方案。
laravel scout 的 `builder` 不支持 `union()`,需通过自定义查询、集合合并或引擎扩展(如 algolia scout extended)来实现多模型混合搜索与分页。本文详解三种兼容主流驱动的生产级解决方案。
在使用 Laravel Scout 进行全文搜索时,一个常见需求是跨多个模型(如 Post 和 Page)统一检索并混合分页展示结果。但直接对 Scout\Builder 实例调用 union() 会抛出 Method Laravel\Scout\Builder::union does not exist 错误——这是因为 Scout 的查询构建器抽象了底层搜索引擎(如 Algolia、Meilisearch、Database),而 union 是 Eloquent 原生 SQL 操作,无法跨引擎通用。以下是三种经过验证、可落地的解决方案:
✅ 方案一:利用 query() 自定义底层 Eloquent 查询(推荐用于 Database 驱动)
当使用 scout:driver => 'database' 时,可借助 Scout 提供的 query() 方法“接管”最终的 Eloquent 查询,将另一个模型的查询以 UNION 方式注入:
public function search($term)
{
// 构建 Post 的原始 Eloquent 查询(注意:此处不走 Scout 索引,仅作 SQL UNION)
$postQuery = Post::query()
->where('title', 'like', "%{$term}%");
// 使用 Page 的 Scout 搜索,并通过 query() 注入 Union
return Page::search($term)
->query(fn ($builder) => $builder->union($postQuery))
->orderBy('created_at', 'desc')
->paginate(15);
}⚠️ 注意事项:
- 此方案要求所有 UNION 表结构兼容(列数、类型、顺序一致),建议统一 select 字段(如 select('id', 'title as name', 'created_at', 'type'))并添加模型标识字段便于前端区分;
- 仅适用于 database 驱动;其他引擎(如 Meilisearch)不支持此方式。
✅ 方案二:合并 Scout 结果集 + 自定义 Collection 分页(通用兼容)
适用于任意 Scout 驱动(包括 Algolia/Meilisearch)。核心思路:分别执行 Scout 搜索 → 合并为单一集合 → 手动分页:
首先,在 App\Providers\AppServiceProvider@boot() 中注册 Collection::paginate() 宏:
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
Collection::macro('paginate', function ($perPage = 15, $total = null, $page = null, $pageName = 'page') {
$page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
$start = ($page - 1) * $perPage;
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$total ?: $this->count(),
$perPage,
$page,
['path' => LengthAwarePaginator::resolveCurrentPath(), 'pageName' => $pageName]
);
});然后在控制器中组合结果:
public function search($term)
{
// 并行执行 Scout 搜索(高效且利用索引)
$posts = Post::search($term)->get();
$pages = Page::search($term)->get();
// 合并、排序、分页(注意:sortBy() 返回新集合,需 reindex)
return $posts
->concat($pages)
->sortByDesc('created_at')
->values() // 重置键名,确保分页正确
->paginate(15);
}✅ 优势:完全驱动无关,保留 Scout 全文检索能力;
⚠️ 注意:内存敏感场景需限制单次搜索数量(如 .take(100)),避免大结果集 OOM。
✅ 方案三:使用 Algolia Scout Extended(Algolia 用户专属)
若项目已采用 Algolia,强烈推荐 Algolia Scout Extended —— 它原生支持多模型聚合搜索:
composer require algolia/scout-extended
配置后,一行代码即可实现跨模型搜索与分页:
use Laravel\Scout\Builder;
$models = [Post::class, Page::class];
$results = Builder::search($term)
->within($models)
->paginate(15);该方案自动处理模型映射、结果去重与排序,性能最优,但仅限 Algolia 生态。
? 总结与选型建议
| 方案 | 适用驱动 | 是否利用索引 | 性能 | 复杂度 |
|---|---|---|---|---|
| query() + union | database | ❌(部分回退 SQL) | ⚡ 高 | ⭐⭐ |
| Collection 合并分页 | 全驱动 | ✅ | ⚡ 中(内存可控) | ⭐⭐⭐ |
| Algolia Scout Extended | Algolia | ✅ | ⚡⚡⚡ 最优 | ⭐ |
? 最佳实践提示:无论选用哪种方案,务必在模型中实现 toSearchableArray() 显式控制索引字段,并为 created_at 等排序字段添加数据库索引,以保障分页性能。同时,前端应识别 model_type 或 source 字段区分结果来源,提升用户体验。

















