konekt/search

Laravel 包,用于在多个 Eloquent 模型中进行搜索。支持分页、预加载关系、单/多列搜索、排序和范围查询。

1.4.0 2024-04-25 14:50 UTC

This package is auto-updated.

Last update: 2024-08-25 15:38:16 UTC


README

Tests Packagist version Packagist downloads StyleCI MIT Software License

此包是基于优秀的 Laravel Cross Eloquent Search 包的一个分支。
请考虑 赞助此包的原始作者

此 Laravel 包允许您在多个 Eloquent 模型中进行搜索。它支持排序、分页、范围查询、预加载关系以及单或多列搜索。

要求

  • PHP 8.0+
  • MySQL 8.0(所有功能)
  • PostgreSQL 11+(部分功能集)
  • SQLite(部分功能集)
  • Laravel 9.x, 10.x, 11.x

功能矩阵

功能

  • 搜索一个或多个 Eloquent 模型
  • 支持跨模型 分页
  • 搜索单列或多列。
  • 搜索(嵌套)关系。
  • 支持全文搜索,即使是通过关系。
  • 按(跨模型)列或相关性排序。
  • 使用 约束范围查询
  • 为每个模型预加载 关系
  • 在数据库中对组合结果进行 排序
  • 零第三方依赖

安装

您可以通过 composer 安装此包

composer require konekt/search
  • 如果您想使用 PostgreSQL 的 soundsLike() 相似度搜索,则需要在指定的数据库上运行 CREATE EXTENSION pg_trgm; 一次,以启用 Trigram 扩展

用法

首先将一个或多个模型添加到搜索中。使用模型类名和您要搜索的列调用 add 方法。然后使用搜索词调用 search 方法,您将获得一个包含结果的 \Illuminate\Database\Eloquent\Collection 实例。

默认情况下,结果按 updated 列的升序排序。在大多数情况下,此列是 updated_at。如果您已经 自定义 模型的 UPDATED_AT 常量,或覆盖了 getUpdatedAtColumn 方法,此包将使用自定义的列。如果您根本不使用时间戳,则默认使用主键。当然,您也可以按其他列 排序

use Konekt\Search\Facades\Search;

$results = Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->search('howto');

如果您关心缩进,可以可选地使用 facade 上的 new 方法

Search::new()
    ->add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->search('howto');

还有一个 when 方法,可以根据其他条件应用某些子句

Search::new()
    ->when($user->isVerified(), fn($search) => $search->add(Post::class, 'title'))
    ->when($user->isAdmin(), fn($search) => $search->add(Video::class, 'title'))
    ->search('howto');

通配符

默认情况下,我们会将搜索词拆分,每个关键词都会获得通配符以进行部分匹配。实际上这意味着搜索词 apple ios 将产生 apple%ios%。如果你想让通配符从开始就出现,可以调用 beginWithWildcard 方法。这将产生 %apple%%ios%

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->beginWithWildcard()
    ->search('os');

如果你想要禁用向词尾添加通配符的行为,应该调用 endWithWildcard 方法并传入 false

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->beginWithWildcard()
    ->endWithWildcard(false)
    ->search('os');

多词搜索

多词搜索默认支持。只需将短语用双引号括起来即可。

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->search('"macos big sur"');

你可以通过调用 dontParseTerm 方法来禁用搜索词的解析,这会给你与使用双引号相同的结果。

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->dontParseTerm()
    ->search('macos big sur');

排序

如果你想要按另一列排序结果,可以将该列传递给 add 方法作为第三个参数。调用 orderByDesc 方法可以按降序排序结果。

Search::add(Post::class, 'title', 'published_at')
    ->add(Video::class, 'title', 'released_at')
    ->orderByDesc()
    ->search('learn');

你可以调用 orderByRelevance 方法来按搜索词出现的次数排序结果。

  • 想象这两句话
  • 苹果公司推出iPhone 13和iPhone 13 mini

苹果公司推出具有突破性性能的全新iPad mini,设计令人惊叹

Search::add(Post::class, 'title')
    ->beginWithWildcard()
    ->orderByRelevance()
    ->search('Apple iPad');

如果你在搜索(嵌套)关系时,不支持按相关性排序。

要按型号类型排序结果,可以使用 orderByModel 方法,并传入你喜欢的型号顺序

Search::new()
    ->add(Comment::class, ['body'])
    ->add(Post::class, ['title'])
    ->add(Video::class, ['title', 'description'])
    ->orderByModel([
        Post::class, Video::class, Comment::class,
    ])
    ->search('Artisan School');

分页

我们强烈建议对结果进行分页。在调用 search 方法之前调用 paginate 方法,你将得到一个 \Illuminate\Contracts\Pagination\LengthAwarePaginator 实例作为结果。paginate 方法接受三个(可选)参数以自定义分页器。这些参数与Laravel的数据库分页器相同。

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')

    ->paginate()
    // or
    ->paginate($perPage = 15, $pageName = 'page', $page = 1)

    ->search('build');

你也可以使用 简单分页。这将返回一个 \Illuminate\Contracts\Pagination\Paginator 实例,它不是长度感知的

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')

    ->simplePaginate()
    // or
    ->simplePaginate($perPage = 15, $pageName = 'page', $page = 1)

    ->search('build');

约束和范围查询

除了类名外,你还可以将 Eloquent查询构建器 的实例传递给 add 方法。这允许你向每个模型添加约束。

Search::add(Post::published(), 'title')
    ->add(Video::where('views', '>', 2500), 'title')
    ->search('compile');

每个模型的多列

你可以通过将列的数组作为第二个参数传递来搜索多个列。

Search::add(Post::class, ['title', 'body'])
    ->add(Video::class, ['title', 'subtitle'])
    ->search('eloquent');

搜索(嵌套)关系

你可以通过使用 表示法来搜索(嵌套)关系

Search::add(Post::class, ['comments.body'])
    ->add(Video::class, ['posts.user.biography'])
    ->search('solution');

全文搜索

你可以使用 MySQL的全文搜索 通过调用 addFullText 方法。你可以搜索单个或多个列(使用 全文索引),并且你可以指定一组选项,例如指定模式。你甚至可以在一个查询中混合常规搜索和全文搜索

Search::new()
    ->add(Post::class, 'title')
    ->addFullText(Video::class, 'title', ['mode' => 'boolean'])
    ->addFullText(Blog::class, ['title', 'subtitle', 'body'], ['mode' => 'boolean'])
    ->search('framework -css');

如果你要搜索关系,你需要传入一个数组,其中数组的键包含关系,而值是一个包含列的数组

Search::new()
    ->addFullText(Page::class, [
        'posts' => ['title', 'body'],
        'sections' => ['title', 'subtitle', 'body'],
    ])
    ->search('framework -css');

听起来像

MySQL内置了 soundex 算法,因此你可以搜索听起来几乎相同的术语。你可以通过调用 soundsLike 方法使用此功能

Search::new()
    ->add(Post::class, 'framework')
    ->add(Video::class, 'framework')
    ->soundsLike()
    ->search('larafel');

预加载关系

这里没有太多要解释的,但这也得到了支持 :)

Search::add(Post::with('comments'), 'title')
    ->add(Video::with('likes'), 'title')
    ->search('guitar');

不搜索而获取结果

在调用 search 方法时未提供术语或提供了空术语。在这种情况下,您可以忽略 add 方法的第二个参数。使用 orderBy 方法,您可以设置要按列排序的字段(之前是第三个参数)

Search::add(Post::class)
    ->orderBy('published_at')
    ->add(Video::class)
    ->orderBy('released_at')
    ->search();

记录计数

您可以使用 count 方法来计算结果数量

Search::add(Post::published(), 'title')
    ->add(Video::where('views', '>', 2500), 'title')
    ->count('compile');

模型标识符

您可以使用 includeModelType 方法将模型类型添加到搜索结果中。

Search::add(Post::class, 'title')
    ->add(Video::class, 'title')
    ->includeModelType()
    ->paginate()
    ->search('foo');

// Example result with model identifier.
{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "video_id": null,
            "title": "foo",
            "published_at": null,
            "created_at": "2021-12-03T09:39:10.000000Z",
            "updated_at": "2021-12-03T09:39:10.000000Z",
            "type": "Post",
        },
        {
            "id": 1,
            "title": "foo",
            "subtitle": null,
            "published_at": null,
            "created_at": "2021-12-03T09:39:10.000000Z",
            "updated_at": "2021-12-03T09:39:10.000000Z",
            "type": "Video",
        },
    ],
    ...
}

默认情况下,它使用 type 键,但您可以通过传递键到方法中来自定义此设置。

您还可以通过向您的模型添加一个公共方法 searchType() 来覆盖默认类基名,以自定义 type 值。

class Video extends Model
{
    public function searchType()
    {
        return 'awesome_video';
    }
}

// Example result with searchType() method.
{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "video_id": null,
            "title": "foo",
            "published_at": null,
            "created_at": "2021-12-03T09:39:10.000000Z",
            "updated_at": "2021-12-03T09:39:10.000000Z",
            "type": "awesome_video",
        }
    ],
    ...

独立解析器

您可以使用 parseTerms 方法使用解析器

$terms = Search::parseTerms('drums guitar');

您还可以将回调作为第二个参数传递,以遍历每个术语

Search::parseTerms('drums guitar', function($term, $key) {
    //
});