konekt / search
Laravel 包,用于在多个 Eloquent 模型中进行搜索。支持分页、预加载关系、单/多列搜索、排序和范围查询。
Requires
- php: ^8.0
- ext-pdo: *
- laravel/framework: ^9.0|^10.0|^11.0
- nesbot/carbon: ^2.66|^3.0
Requires (Dev)
- mockery/mockery: ^1.4.4
- orchestra/testbench: ^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.5|^10.5
README
此包是基于优秀的 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) { // });