Laravel Scout + meilisearch安装使用流程步骤

2023-06-01 00:00:00 安装 步骤 流程

本教程将介绍如何开始使用带有 Laravel Scout 的meilisearch,这样你就可以看到设置的不同 - 并决定你想走哪条路。和往常一样,我们将从一个全新的Laravel应用程序开始——我通常使用Laravel 安装程序,因为我经常在本地使用Valet——但本教程应该可以很好地跨valet和docker。


通过运行以下命令之一为此演示创建一个新应用程序:

使用 Laravel 安装程序

laravel new search-demo

使用 Composer 创建项目

composer create-project laravel/laravel search-demo

使用 Laravel 构建和航行

curl -s "https://laravel.build/search-demo" | bash

无论您选择哪种方式运行上述程序,您都会在一个名为的新目录下获得一个 Laravel 项目,search-demo这意味着我们可以开始了。


我们要做的第一件事是通过运行以下 composer 命令安装 Laravel Scout :

composer require laravel/scout

这会将 Scout 安装到我们的 Laravel 应用程序中,以便我们可以开始与我们可能想要用于搜索的任何潜在驱动程序进行交互。

我们的下一步是通过运行以下 artisan 命令发布 Laravel Scout 的配置:

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

这将设置我们,以便我们可以修改新创建config/scout.php的内容,但我们可能想要更改它,但您可能希望保持这个漂亮的标准。

此时我们有几个选项,驱动程序选项。

在本教程中,我们将介绍如何使用meilisearch,但您可以使用以下选项作为 Laravel Scout 驱动程序:

Algolia:使用 algolia 3rd 方服务。
meilisearch:使用开源的 meilisearch 服务。
收藏:将数据库用作搜索服务——仅支持 MySQL 和 PostgreSQL。
null:不使用驱动程序 - 通常用于测试。

要开始使用 meilisearch 驱动程序,我们需要安装一个新的包来允许scout使用 meilisearch SDK,所以运行以下composer 命令:

composer require meilisearch/meilisearch-php

因此,Laravel 文档说您还需要安装,http-interop/http-factory-guzzle但是如果您查看 meilisearch-php 库,它现在已包含在该依赖项中。

所以我们可以跳过这个,或者如果它让你更舒服就安装它。我们的下一步是在我们的.env文件中设置一些 ENV 变量:

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

env 变量是一个有趣的MEILISEARCH_KEY变量,如果您在本地安装 meilisearch,您可以启动服务并在每次运行时传递一个可选标志来设置它。

在生产中,您需要确保将其设置为安全措施,但是如果您愿意,您可以在本地将其留空。

就我个人而言,我保留了这个设置,因为它是一种很好的做法,并提醒我我需要实际设置它。


我们安装了 Laravel Scout,并安装并配置了 meilisearch 客户端。

下一步是考虑数据,就像所有好的应用程序一样——它有点需要它。

对于这个演示,我们将使用一个相当基本的示例,以便我们可以专注于 meilisearch 和 scout 主题,而不会迷失在演示代码逻辑中。

这将是一个简单的博客应用程序,其中我们有博客文章和类别。所以我们可以索引我们需要的一切。

通过运行以下artisan命令创建一个新的Eloquent 模型Category,注意用于创建迁移和工厂的附加标志,这在这里很重要:

php artisan make:model Category -mf

我们的 Category 将是一个相对轻量级的模型,因此我将向您展示下面的迁移代码,并让您自己处理模型,使用fillable或guarded取决于您的个人喜好。

public function up()
{
    Schema::create('categories', static function (Blueprint $table): void {
        $table->id();
 
        $table->string('name');
        $table->string('slug')->unique();
 
$table->boolean('searchable')->default(true);
 
        $table->timestamps();
    });
}


我们这里有一个名称、slug 和可搜索的布尔标志。这使我们可以将特定类别完全隐藏在我们的搜索中——这有时可能很有用。

填写您的 Eloquent 模型,但您通常会这样做,下一步是创建模型工厂:

class CategoryFactory extends Factory
{
    protected $model = Category::class;
 
    public function definition(): array
    {
        $name = $this->faker->unique()->word();
 
        return [
            'name' => $name,
            'slug' => Str::slug(
                title: $name,
            ),
            'searchable' => $this->faker->boolean(
                chanceOfGettingTrue: 85,
            ),
        ];
    }
 
public function searchable(): static
    {
        return $this->state(fn (array $attributes): array => [
            'searchable' => true,
        ]);
    }
 
public function nonsearchable(): static
    {
        return $this->state(fn (array $attributes): array => [
            'searchable' => false,
        ]);
    }
}

我们将类别可搜索的默认可能性设置得非常高,但随后我们添加了额外的状态方法,以允许我们控制这种可能性以用于测试目的。

这为我们提供了测试实现的最佳覆盖率。

接下来,我们将需要另一个名为 Model的模型Post,这将是我们搜索的主要入口点,因此运行以下 artisan 命令:

php artisan make:model Post -mf

再次像以前一样,我将向您展示迁移,并让您处理模型上的可填充或受保护的属性 - 因为这是非常个人的偏好。

public function up(): void
{
    Schema::create('posts', static function (Blueprint $table): void {
        $table->id();
 
        $table->string('title');
        $table->string('slug')->unique();
        $table->mediumText('content');
 
$table->boolean('published')->default(true);
 
        $table
->foreignId('category_id')
->index()->constrained()->cascadeOnDelete();
 
        $table->timestamps();
    });
}


下一步是为我们的 Post 模型填写我们的 Factory:

class PostFactory extends Factory
{
    protected $model = Post::class;
 
    public function definition(): array
    {
        $title = $this->faker->unique()->sentence();
 
        return [
            'title' => $title,
            'slug' => Str::slug(
                title: $title,
            ),
            'content' => $this->faker->paragraph(),
            'published' => $this->faker->boolean(
                chanceOfGettingTrue: 85,
            ),
            'category_id' => Category::factory(),
        ];
    }
 
    public function published(): static
    {
        return $this->state(fn (array $attributes): array => [
            'published' => true,
        ]);
    }
 
    public function draft(): static
    {
        return $this->state(fn (array $attributes): array => [
            'published' => false,
        ]);
    }
}

与 Category 模型一样,我们有一个布尔标志 - 但这次是用于模型是否发布 - 以便我们可以拥有一个草稿状态。我们将额外的状态方法添加到我们的工厂中,以便我们在测试环境中很好地控制它。

最后,您可以将关系添加到您的模型中,您的类别应该是HasMany帖子,您的帖子应该是BelongsTo类别。

现在我们的数据已全部建模并可以使用,我们希望能够播种一些数据。

但在我们这样做之前,我们需要安装 meilisearch。如果你使用的是 Laravel Sail,那么当你告诉sails 安装时,它就像传递一个选项一样简单——但是使用 Laravel Valet 时它有点不同。这个安装说明在meilisearch 文档上,并且相对容易遵循,任何问题确保您检查在本地运行 meilisearch 的要求。


假设您现在已经启动并运行了 meilisearch,让我们看看播种一些数据。

我将在我的播种机中添加一个进度条,以便我知道它工作正常,但如果您不想这样做,请随意跳过此步骤:

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;
 
    public function run(): void
    {
       $categories = Category::factory(10)->create();
        
       $categories->each(function (Category $category) {
       $this->command->getOutput()->info(
           message: "Creating posts for category: [$category->name]",
       );
        
       $bar = $this->command->getOutput()->createProgressBar(100);
        
       for ($i = 0; $i < 100; $i++) {
       $bar->advance();
       Post::factory()->create();
       }
        
       $bar->finish();
       });
    }
}

我现在不想对我的播种产生任何副作用,因为我想控制这种行为——所以我使用这个WithoutModelEvents特性来阻止这些。

我们在这里所做的是创建 10 个类别,然后为每个类别创建一个进度条并为该类别创建 100 个帖子。

这在运行播种机时提供了视觉输出,并确保每个类别都有帖子 - 所以当我们搜索时,我们可以看到我们有什么可用的。


现在我们有了一些数据,我们可以看看让我们的 Post 模型可搜索。

为此,我们需要做的就是将SearchableLaravel Scout 的 trait 添加到我们的模型中:

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
    // Other model stuff here ...
}

现在我们的模型是可搜索的,我们可以开始向我们的模型添加一些控件,以了解我们希望它如何被搜索。

几乎 99% 的时间我想使用我的 Post 模型,我也需要类别 - 所以我会告诉 Eloquent 模型总是在它旁边加载类别模型。

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
protected $with = [
'category'
];
 
// Other model stuff here ...
}

现在我们可以添加一个新方法来允许 Laravel Scout 检查模型是否可以被搜索或添加到索引中:

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
protected $with = [
'category'
];
 
public function searchable(): bool
{
    return $this->published || $this->category->searchable;
}
 
// Other model stuff here ...
}

如果我们的帖子已发布,或者属于可搜索的类别 - 我们希望将其编入索引。

这将允许 Scout 在更新时重新评估该模型是否需要被索引。

下一步是控制我们希望它如何索引,我们不必担心索引名称——因为我通常将此作为小型应用程序的标准——但您可以使用该searchableAs方法覆盖它并自己设置索引名称。

要控制如何将数据添加到 meilisearch,您需要添加toSearchableArray方法,该方法允许您定义一个数组来索引数据:

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
   protected $with = [
   'category'
   ];
 
   public function searchable(): bool
   {
       return $this->published || $this->category->searchable;
   }
 
   public function toSearchableArray(): array
   {
       return [
           'title' => $this->title,
           'slug' => $this->slug,
           'content' => $this->content,
           'category' => [
               'name' => $this->category->name,
   'slug' => $this->category->slug,
           ]
       ];
   }
    
   // Other model stuff here ...
}

我们希望将类别信息添加到每个帖子,以便我们可以在我们的 UI 上正确显示帖子本身的信息,例如“帖子标题(类别名称)”或类似的东西。

最后我们有了可以索引和搜索的东西,所以让我们将所有 Post 记录导入 meilisearch:

php artisan scout:import "App\Models\Post"

这应该显示所有被添加到侦察的 500 条记录块的输出。所以现在我们有一些东西要搜索,我们需要考虑我们想要如何搜索。

对于 Scout,您可以使用模型上的静态搜索方法进行简单搜索 - 您向其传递查询并返回水合模型,或者您可以开始查看过滤器等。

因此,让我们看一下控制器内部的基本搜索并从那里进行重构。

class SearchController extends Controller
{
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: Post::search(
                query: trim($request->get('search')) ?? '',
            )->get(),
            status: Response::HTTP_OK,
        );
    }
}

现在让我们在我们的 api 路由下注册这个路由,这样我们就可以在不创建 UI 的情况下查看结果。

Route::get(
    'search',
    App\Http\Controllers\SearchController::class
)->name('search');

现在我们可以根据搜索查询参数查看我们搜索的 JSON 输出,看看并测试它是如何响应的。尝试搜索整个单词和部分单词。

这是 Laravel Scout 和 meilisearch 的基础知识,我们现在可以索引模型并针对它们进行搜索 - 所以从这个角度来看我们很好。

下一步是考虑如何才能获得更多。

过滤器是很棒的东西,它允许我们在搜索中获得更多有针对性的结果,只需请求它们。

因此,我们将向 Post 模型添加一些过滤器,以便我们可以轻松过滤查询。

这是我的方法,虽然它不一定是你的,所以用一点点盐来适应我即将做的事情并适应你自己的需要。

class Post extends Model
{
    use Searchable;
    use HasFactory;
 
   protected $with = [
   'category'
   ];
    
   public function searchable(): bool
   {
       return $this->published || $this->category->searchable;
   }
    
   public function toSearchableArray(): array
   {
       return [
           'title' => $this->title,
           'slug' => $this->slug,
           'content' => $this->content,
           'category' => [
               'name' => $this->category->name,
   'slug' => $this->category->slug,
           ]
       ];
   }
    
   public static function getSearchFilterAttributes(): array
   {
       return [
           'category.name',
           'category.slug',
       ];
   }
    
   // Other model stuff here ...
}

我添加了一个静态函数来为我的模型定义搜索过滤器属性,如您所见,我希望能够按类别名称或 slug 进行过滤。

下一步是创建一个命令来向 meilisearch 注册这些可过滤的属性。

我通常会创建一个控制台命令来执行此操作,因为默认情况下 scout 无法执行此操作:

php artisan make:command Search/SetupSearchFilters

然后添加以下代码片段:

class SetupSearchFilters extends Command
{
    protected $signature = 'scout:filters
   {index : The index you want to work with.}
   ';
    
       protected $description = 'Register filters against a search index.';
    
       public function handle(Client $client): int
           {
               $index = $this->argument(
                   key: 'index',
               );
        
               $model = match($index) {
                   'posts' => Post::class,
               };
        
               try {
                   $this->info(
                       string: "Updating filterable attributes for [$model] on index [$index]",
                   );
        
                   $client->index(
                       uid: $index,
                   )->updateFilterableAttributes(
                       filterableAttributes: $model::getSearchFilterAttributes(),
                   );
               } catch (ApiException $exception) {
                   $this->warn(
                       string: $exception->getMessage(),
                   );
        
                   return self::FAILURE;
               }
        
               return 0;
           }
}

我们在这里所做的是传递一个索引,以防我们扩展我们想要索引的内容,然后使用 match/switch 语句将其与模型匹配。

然后由于控制台命令的工作方式,我们可以在我们的 handle 方法中解析 meilisearch 客户端 - 并使用它来更新索引,同时获取搜索过滤器属性。

如果失败,我们会显示异常并返回失败。


现在我们可以使用以下命令运行它:

php artisan scout:filters 'posts'

如果一切按计划进行,meilisearch 现在将了解您的索引上可用的过滤器。

那么让我们来看看我们能不能做到呢?

现在我们将重构 SearchController 以接受过滤器进入搜索。

class SearchController extends Controller
{
    public function __invoke(Request $request): JsonResponse
    {
        return new JsonResponse(
            data: Post::search(
                query: $request->get('search'),
                   callback: function (Indexes $meilisearch, string $query, array $options) use ($request) {
                   if ($request->has(key: 'categry.slug')) {
                       $options['filter'] = "category.slug = {$request->get(key: 'category.slug')}";
                   }
                    
                   return $meilisearch->search(
                       query: $query,
                       options: $options,
                       );
                   },
            )->get(),
            status: Response::HTTP_OK,
        );
    }
}

现在,如果您在搜索中添加另一个查询参数,category.slug={something}那么您应该得到您正在执行的搜索的过滤结果,我的当前看起来像:

/api/search?search=rem&category.slug=voluptatibus

它很好地过滤了结果。

您可以根据需要扩展这些,包括类别名称的过滤器,甚至更多,具体取决于您选择如何对数据进行建模。如果需要,您甚至可以创建过滤器以根据时间进行过滤。

这只是您可以在应用程序中使用 Laravel Scout 实现出色搜索的一种方式,并在需要时使用过滤器对其进行微调。

Laravel Scout 有许多可用的驱动程序,并且创建自己的驱动程序并非不可能 - 事实上,如果它们适合您的用例,您可以使用一些开源驱动程序!


您如何处理对您的应用程序的搜索?你试过meilisearch吗?在推特上告诉我们,让我们知道您是如何找到这篇文章的!


转:

https://laravel-news.com/getting-started-laravel-scout-meilisearch

相关文章