onurakman/laravel-meilisearch

简单的Laravel封装,用于使用Meilisearch

2.0.3 2023-02-04 13:24 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版本0.27时,使用以下命令

composer require eelcol/laravel-meilisearch:<=1

当使用Meilisearch版本0.28时

composer require eelcol/laravel-meilisearch:^1.0

设置.env

将您的.env更改为包括以下变量

MEILISEARCH_HOST=...
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中的主数据

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');

使用查询构建器

如果您想应用过滤或排序,我建议使用查询构建器。您可以在测试文件夹中查看一些示例。下面列出了几个简单示例。

根据属性过滤

可以使用 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。

在 ... 中

这与数组配合得最好。例如,您有一个具有多个类别的产品

[
    '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 方法将检查 至少有一个值 存在于模型上。因此,上述查询将返回 所有 文档。

使用 '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();

使用分面

被标记为 filterable 的列可以用作分面。查询构建器将返回这些分面,并附带产品计数。可以通过使用 setFacetsaddFacet 方法来定义分面

MeilisearchQuery::index('products')
    ->where('categories', '=', 'phones')
    ->setFacets([
        'color',
        'brand'
    ])
    ->get();

MeilisearchQuery::index('products')
    ->where('categories', '=', 'phones')
    ->addFacet('color')
    ->addFacet('brand')
    ->get();

析取分面分布

在当前版本的 Meilisearch 中,当您对属性进行过滤时,不会返回该属性的分面。请参阅以下讨论:meilisearch/product#187

例如,当您运行上述查询时,将返回颜色 greysilvergoldyellow。接下来,您只想显示颜色为 yellow 的产品。因此,您应用了一个过滤器

MeilisearchQuery::index('products')
    ->where('categories', '=', 'phones')
    ->where('color', '=', 'yellow')
    ->setFacets([
        'color',
        'brand'
    ])
    ->get();

但是,当您这样做时,分面 color 现在将仅返回 yellow。这使得向最终用户显示所有可能的颜色变得更加困难。这就是为什么这个包有一个 keepFacetsInMetadata 方法。您可以在其中应用过滤器,这些过滤器在检索元数据时将不会应用。

当使用 keepFacetsInMetadata 方法时,该包将创建两个 Meilisearch 查询。一个查询应用了所有过滤器来获取产品,另一个查询应用了部分过滤器来获取元数据(分面)。

MeilisearchQuery::index('products')
    ->where('categories', '=', 'phones')
    ->keepFacetsInMetadata(function ($q) {
        $q->where('color', '=', 'yellow');
    })
    ->setFacets([
        'color',
        'brand'
    ])
    ->get();

这样,返回的数据将包含在 phones 类别中的产品上可用的所有分面。因此,您可以在对颜色进行过滤时轻松显示所有可用的颜色。

请注意,此方法将生成另一个查询。因为大多数时候,Meilisearch 查询都非常快(< 10ms),我相信这不会对网站速度造成任何重大影响。

限制和偏移量

可以轻松地将限制和偏移量添加到查询中。以下查询将返回 10 个结果,从第 20 个结果开始

MeilisearchQuery::index('products')
    ->where('categories', '=', 'phones')
    ->limit(10)
    ->offset(20)
    ->get();

分页结果

就像使用 Laravel 查询构建器进行数据库查询一样,您可以分页来自 Meilisearch 的结果。只需使用 paginate 方法即可。当使用此方法时,之前的 limitoffset 调用将被忽略。

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();