eelcol / laravel-meilisearch
Meilisearch 的简单 Laravel 封装
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.4
- illuminate/http: ^8|^9|^10|^11
- illuminate/support: ^8|^9|^10|^11
Requires (Dev)
- orchestra/testbench: ^6.0
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-08-29 14:27:10 UTC
README
当你想在 Laravel 应用中使用 Meilisearch 时,可以使用 Laravel Scout。这是一种简单的方式,将你的模型同步到 Meilisearch 并快速搜索模型。然而,有时 Laravel Scout 并不足以满足需求。例如,如果你想要
- 更多控制你的 Meilisearch 数据库:例如不仅保存模型。
- 设置可搜索、可筛选或可排序的属性。
- 向 Meilisearch 执行更复杂的查询,例如使用多个筛选器。
- 使用查询构建器从 Meilisearch 数据库中检索数据。
- 使用 Meilisearch 不提供的一些功能。例如,以随机顺序显示文档或在筛选文档中显示不可用的分面。
此包处理这类情况。你决定向 Meilisearch 发送哪些信息,以及你想得到哪些信息。专为 Meilisearch 定制的查询构建器有助于构建更复杂的查询。
使用此包时,你应该自己决定何时以及发送哪些数据到 Meilisearch。所以如果你想在模型保存或创建后自动将模型发送到 Meilisearch,Laravel Scout 可能是更好的解决方案。
与 Meilisearch 的兼容性
目前,此包支持 Meilisearch 0.27 版本。Meilisearch 0.28 版本引入了一些重大更改。兼容 0.28 版本的新版本将很快发布。
安装
根据 Meilisearch 版本确定需要哪个版本
例如,当使用 Meilisearch 1.0.2 时,使用以下命令
composer require eelcol/laravel-meilisearch:~2.0.0
设置 .env
修改 .env 以包括以下变量
MEILISEARCH_HOST=...
MEILISEARCH_KEY=...
如果不使用 Meilisearch 密钥,.env 变量 MEILISEARCH_KEY 可以是任何值。
发布资源
php artisan vendor:publish --tag=laravel-meilisearch
入门
创建索引
首先你需要创建一个索引来保存文档。例如,你需要一个索引来保存我们的产品目录。所以可以使用以下命令
php artisan meilisearch:create-index products
此命令将创建一个文件 database/meilisearch/products.php
。在这个文件中,你可以调整此索引的设置。这不是必须的,但它非常推荐。如果你保留标准设置,Meilisearch 将使用你的数据的所有列进行搜索。为了实现这一点,Meilisearch 必须索引你的数据的所有列。这将花费更多时间,并使用更多服务器资源。这就是为什么推荐指定哪些列应该是可搜索的、可筛选的和可排序的。
每次你想更改设置时,只需更改此文件。更改后,运行以下命令。
将索引迁移到 Meilisearch 数据库
现在必须实际创建索引。为此,运行以下命令
php artisan meilisearch:set-index-settings
这与 Laravel 的数据库迁移类似。首先你必须创建一个数据库迁移,然后运行迁移以实际创建表或进行调整。
每次你更改 database/meilisearch/products.php
文件时,都要运行此命令。还要在每次部署时运行此命令,以确保生产中有最新的 Meilisearch 实例。
如果你想在不同的 Meilisearch 安装上设置索引设置,可以使用 --mshost
和 --mskey
选项
php artisan meilisearch:set-index-settings --mshost=http://another-meilisearch-installation:7700 --mskey=secret-key
Meilisearch 中的主数据
Meilisearch 文档中提到的所有功能都包含在此包中。最重要的功能列在下述
插入数据
要在索引products
中插入文档,你可以执行以下操作之一:
Meilisearch::addDocument('products', [ 'id' => 1, 'title' => 'iPhone SE' ]);
Meilisearch::addDocuments('products', [ [ 'id' => 1, 'title' => 'iPhone SE' ], [ 'id' => 2, 'title' => 'Samsung Galaxy' ] ]);
你也可以直接插入一个模型或集合。一个模型会被转换成数组。为了做到这一点,该包会检查对象上是否存在以下方法,顺序如下:
- toMeilisearch() - toSearchableArray() - toArray()
例如,一个Product
模型可以看起来像这样:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; class Product extends Model { use HasFactory; protected $guarded = ['id']; public function toMeilisearch(): array { return [ 'id' => $this->id, 'title' => $this->title, 'slug' => Str::slug($this->title), ]; } }
模型可以按如下方式插入:
$product = App\Models\Product::find(1); Meilisearch::addDocument('products', $product); // the product will be inserted like: // [ // 'id' => 1, // 'title' => 'iPhone SE', // 'slug' => 'iphone-se' // ]
集合也可以直接插入
$products = App\Models\Product::all(); Meilisearch::addDocuments('products', $products);
检索数据
可以使用getDocuments
方法检索索引中的文档。当你想应用过滤器时,建议使用查询构建器。数据将自动分页。
$documents = Meilisearch::getDocuments('products');
删除文档
可以使用deleteDocument
或deleteDocuments
方法通过ID删除文档。这两个方法都返回一个MeilisearchTask
对象。
$task = Meilisearch::deleteDocument(index: 'products', id: 1); $task = Meilisearch::deleteDocuments(index: 'products', ids: [1,2,3]);
也可以使用查询构建器来删除文档,请参阅下面的内容。
检索分面值
你可以检索给定分面所有可用的值。例如,下面的代码将搜索所有包含字母'a'的商标。
$collection = Meilisearch::searchFacetValues(index: 'products', facetName: 'brand', facetQuery: 'a');
使用查询构建器
如果你想应用过滤或排序,我建议使用查询构建器。你可以在测试文件夹中查看一些示例。以下是一些简单的示例。
基于属性过滤
可以使用where
方法进行简单的过滤
$documents = MeilisearchQuery::index('products') ->where('title', '=', 'iPhone SE') ->get(); $documents = MeilisearchQuery::index('products') ->where('price', '<', 100) ->get();
多个wheres
也可以组合使用
$documents = MeilisearchQuery::index('products') ->where('title', '=', 'iPhone SE') ->where('price', '<', 100) ->get();
使用'or'进行过滤
目前,在顶层使用'OR'进行过滤是不可行的。如果你想使用'OR'进行过滤,你必须首先创建一个'where-group'。以下调用将引发错误
$documents = MeilisearchQuery::index('products') ->where('title', '=', 'iPhone SE') ->orWhere('title', '=', 'Samsung Galaxy') ->get();
但是以下代码将有效
$documents = MeilisearchQuery::index('products') ->where(function ($q) { $q->where('title', '=', 'iPhone SE'); $q->orWhere('title', '=', 'Samsung Galaxy'); }) ->get();
这是因为Meilisearch过滤的工作方式以及此包如何呈现过滤器的结果。它还防止了在组合'AND'和'OR'语句时可能出现的潜在问题。例如,以下查询可能会返回意外的结果
$documents = MeilisearchQuery::index('products') ->where('title', '=', 'iPhone SE') ->orWhere('title', '=', 'Samsung Galaxy') ->where('price', '<', 100) ->get();
这个查询应该是这样的
- (title = 'iPhone SE' OR title = 'Samsung Galaxy') AND price < 100 - title = 'iPhone SE' OR (title = 'Samsung Galaxy' AND price < 100) - etc...
所以现在,在使用'OR'语句时,你应该首先开始一个where
-group。
Where in
这最适合与数组一起使用。例如,你有一个包含多个类别的产品
[ 'id' => 1, 'title' => 'iPhone SE', 'categories' => [ 'phones', 'smartphones', 'iphones' ], 'id' => 2, 'title' => 'Samsung Galaxy', 'categories' => [ 'phones', 'smartphones', 'samsung' ], ]
这些数据可以查询
MeilisearchQuery::index('products') ->whereIn('categories', ['phones', 'iphones']) ->get();
whereIn
方法将检查至少有一个值在模型上存在。所以上面的查询将返回所有文档。
Where matches
whereIn
方法将检查至少有一个值存在于模型上。而whereMatches
方法将检查所有值都存在于模型上
// this query will return both iPhone SE and Samsung Galaxy MeilisearchQuery::index('products') ->whereMatches('categories', ['phones', 'smartphones']) ->get(); // this query will return ONLY the iPhone SE MeilisearchQuery::index('products') ->whereMatches('categories', ['phones', 'iphone']) ->get(); // this query will return ONLY the Samsung Galaxy MeilisearchQuery::index('products') ->whereMatches('categories', ['phones', 'samsung']) ->get();
空数据
可以使用whereIsEmpty
或whereNotEmpty
来选择具有给定属性空值的文档。这匹配以下JSON值:"", [], {}
。
可以使用whereNull
或whereNotNull
来选择具有给定属性NULL
值的文档。
MeilisearchQuery::index('products') ->whereEmpty('brand') ->get(); MeilisearchQuery::index('products') ->whereNull('brand') ->get();
使用分面
被标记为filterable
的列可以用作分面。查询构建器将返回这些分面以及附加的产品计数。可以通过使用setFacets
或addFacet
方法来定义分面。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->setFacets([ 'color', 'brand' ]) ->get(); MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->addFacet('color') ->addFacet('brand') ->get();
使用查询构建器删除文档
你可以在查询上使用过滤器来删除文档。查询上的其他元素,如分页或排序,将不会应用。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->delete();
上面的查询将删除所有具有类别phones
的产品。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->orderBy('title') ->limit(20) ->delete();
上面的查询与另一个查询完全相同!记住:当你使用查询构建器删除文档时,只有过滤器会被应用。限制、排序和其他操作将不会应用。
指定要搜索的属性
你可以指定每个查询要搜索的属性。默认情况下,查询会搜索在设置文件中指定的所有属性。但如果你想搜索另一组属性,你可以使用searchOnAttributes
方法。这些属性必须是默认搜索属性的一个子集。
例如,当您默认设置了一个搜索标题、描述和品牌
的索引时,您可以执行一个仅搜索标题的查询。
MeilisearchQuery::index('products') ->search("Nike") ->searchOnAttributes(['title']) ->get();
这个查询只找到标题中包含Nike
的产品。如果一个产品标题中没有Nike
,但描述中有,上述查询将不会返回此产品。
析取分面分布
在Meilisearch当前版本中,当您正在对该特定属性进行过滤时,不会返回该属性的分面。请参阅以下讨论:meilisearch/product#187
例如,当您运行上述查询时,返回的颜色有灰色
、银色
、金色
、黄色
。接下来,您只想显示具有黄色
颜色的产品。因此,您应用一个过滤器
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->where('color', '=', 'yellow') ->setFacets([ 'color', 'brand', 'size', ]) ->get();
但是,当您这样做时,分面颜色
现在将仅返回黄色
。这使得向最终用户显示所有可能的颜色变得更加困难。这就是为什么这个包有一个keepFacetsInMetadata
方法。您可以在该方法内部应用过滤器,这些过滤器在获取元数据时将不会被应用。
从Meilisearch版本1.1开始,可以使用多搜索
端点解决这个问题。这就是我在这个包中解决这个问题的方式。包将为查询中使用的每个过滤器执行一个额外的查询。但是,所有查询都在单个请求中组合,以减少所需的资源数量。请看以下示例
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->keepFacetsInMetadata(function ($q) { $q->where('color', '=', 'yellow'); $q->where('size', '=', 'XL'); }) ->setFacets([ 'color', 'brand', 'size', ]) ->get();
此查询将执行以下请求
- 获取所有品牌,其中产品属于手机类别且匹配颜色=黄色和尺寸=XL
- 获取所有颜色,其中产品属于手机类别且匹配尺寸=XL
- 获取所有尺寸,其中产品属于手机类别且匹配颜色=黄色
从Meilisearch版本1.1开始,这是推荐使用多选择分面的方式。
限制和偏移量
限制和偏移量可以轻松地添加到查询中。以下查询将返回10个结果,从第20个结果开始
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->limit(10) ->offset(20) ->get();
分页结果
就像使用Laravel查询构建器对数据库进行查询一样,您可以使用Meilisearch查询分页结果。只需使用paginate
方法。当使用此方法时,较早对limit
和offset
的调用将被忽略。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->paginate(10);
可选地提供要用于获取当前页的查询参数名称。默认使用'page'。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->paginate(10, 'pageNumber');
排序结果
随机顺序
默认情况下,Meilisearch不提供随机排序文档的选项。然而,有时您想显示一些随机产品。为了使这一点成为可能,此包添加了此功能。请注意,包将为每个随机元素向您的Meilisearch数据库执行查询,额外加1次。因此,如果您想以随机顺序获取100个文档,将执行101次查询。Meilisearch查询非常快,但是当您执行此类数量的查询时,它仍然可能会变慢。因此,我建议只在使用少量文档(少于10个)或例如缓存结果时使用此方法。
MeilisearchQuery::index('products') ->where('categories', '=', 'phones') ->inRandomOrder() ->limit(10) ->get();