You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel多模型全局搜索改造:单模型搜索正常,多模型实现遇阻

搞定Laravel多模型全局搜索的正确姿势

嘿,我懂你现在的困扰——单模型搜得好好的,一搞跨表全局搜索就卡壳了。先别急,咱们一步步来解决这个问题。

首先,先给你提个小提醒:你现在的单模型scopeSearch其实藏着个小坑——whereHas和orWhere混用的时候,逻辑优先级会出问题。比如如果之后你加个where('status', 1)这样的条件,orWhere会把它给绕过去。所以先把单模型的scope调整一下,用外层where把所有搜索条件包起来:

public function scopeSearch(Builder $query, $search) {
    $query->where(function ($q) use ($search) {
        $q->whereHas('translations', function ($subQ) use ($search) {
            $subQ->where('value', 'like', '%' . $search . '%');
        })
        ->orWhere('title', 'LIKE', '%' . $search . '%')
        ->orWhere('sub_body', 'like', '%' . $search . '%');
    });
}

好了,单模型的基础打牢了,接下来搞全局搜索。核心思路就是:分别搜索每个模型,然后把结果合并、格式化。

方案一:原生Eloquent实现(无需第三方包)

你可以写一个全局辅助函数或者专门的搜索服务类,来统一处理多模型搜索。比如这样:

1. 创建全局搜索方法

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use App\Models\Post; // 替换成你的实际模型
use App\Models\Product; // 另一个需要搜索的模型

function globalSearch(string $searchTerm): Collection
{
    $combinedResults = new Collection();

    // 搜索第一个模型:Post
    $posts = Post::search($searchTerm)->get();
    if ($posts->isNotEmpty()) {
        $formattedPosts = $posts->map(function ($post) {
            // 优先取内容摘要,没有就用翻译内容
            $summary = $post->sub_body ?? $post->translations->first()?->value;
            return [
                'model_type' => '文章', // 给用户看的友好类型名称
                'title' => $post->title,
                'link' => route('posts.show', $post), // 生成详情页链接
                'summary' => Str::limit($summary, 100), // 截取100字以内的摘要
                'created_at' => $post->created_at
            ];
        });
        $combinedResults = $combinedResults->merge($formattedPosts);
    }

    // 搜索第二个模型:Product
    $products = Product::search($searchTerm)->get();
    if ($products->isNotEmpty()) {
        $formattedProducts = $products->map(function ($product) {
            return [
                'model_type' => '商品',
                'title' => $product->name,
                'link' => route('products.show', $product),
                'summary' => Str::limit($product->description, 100),
                'created_at' => $product->created_at
            ];
        });
        $combinedResults = $combinedResults->merge($formattedProducts);
    }

    // 按创建时间倒序排序,把最新的结果放前面
    return $combinedResults->sortByDesc('created_at')->values();
}

2. 在控制器里调用

public function handleGlobalSearch(Request $request)
{
    $searchTerm = $request->input('search');
    $results = globalSearch($searchTerm);

    return view('search.global-results', compact('results'));
}

方案二:用Laravel Scout简化(适合需要更强大搜索的场景)

如果你的项目需要更高效的搜索(比如分词、权重排序),可以用Laravel官方的Scout扩展包,它支持多模型搜索,还能对接Meilisearch、Algolia这些搜索引擎。

1. 安装并配置Scout

首先安装包:

composer require laravel/scout
# 比如安装Meilisearch驱动(轻量开源,适合中小项目)
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

然后在每个需要搜索的模型里引入Searchable trait,定义搜索字段:

use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    // 定义要索引的字段,把需要搜索的内容都放进来
    public function toSearchableArray()
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'sub_body' => $this->sub_body,
            'translations' => $this->translations->pluck('value')->join(' '),
        ];
    }
}

2. 全局搜索调用

use App\Models\Post;
use App\Models\Product;

public function handleGlobalSearch(Request $request)
{
    $searchTerm = $request->input('search');
    
    // 搜索多个模型并合并结果
    $results = Post::search($searchTerm)->get()
        ->merge(Product::search($searchTerm)->get());

    return view('search.global-results', compact('results'));
}

关于分页的小提示

如果要给全局搜索结果做分页,直接用paginate()会有问题(因为多个模型的分页没法直接合并)。这时候可以手动创建LengthAwarePaginator来处理:

use Illuminate\Pagination\LengthAwarePaginator;

function globalSearchWithPagination(string $searchTerm, int $page = 1, int $perPage = 10): LengthAwarePaginator
{
    $combinedResults = globalSearch($searchTerm); // 调用前面的全局搜索方法
    $offset = ($page - 1) * $perPage;
    
    $paginatedResults = $combinedResults->slice($offset, $perPage)->values();
    
    return new LengthAwarePaginator(
        $paginatedResults,
        $combinedResults->count(),
        $perPage,
        $page,
        ['path' => request()->url(), 'query' => request()->query()]
    );
}

这样就能实现全局搜索的分页功能了。

内容的提问来源于stack exchange,提问作者Ali Özen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:27:59