使用Laravel Scout来实现全文搜索

使用Laravel Scout来实现全文搜索

Laravel框架已经成为开发人员构建网络服务的首选资源。

作为一个开源的工具, Laravel提供了无数的开箱即用的功能, 使开发者能够建立强大的和功能性的应用程序.

在它的产品中,Laravel Scout是一个用于管理你的应用程序的搜索索引的库。它的灵活性让开发者可以微调配置,并选择Algolia, Meilisearch, MySQL, 或Postgres驱动来存储索引。

在这里,我们将深入探讨这个工具,教你如何通过驱动为Laravel应用程序添加全文本搜索支持。你将模拟一个Laravel应用程序,用于存储模拟列车的名称,然后使用Laravel Scout为应用程序添加一个搜索。

  1. 前提条件
  2. 如何在一个Laravel项目中安装Scout
  3. 如何添加Laravel Scout到应用程序中
  4. 如何标记一个模型和配置索引
  5. 如何使用Algolia和Scout
  6. 如何创建应用程序的控制器
  7. 如何创建应用程序的路由
  8. 如何创建应用程序的视图
  9. 如何使用Algolia搜索
  10. 使用Laravel Scout的Meilisearch
  11. Laravel Scout与数据库引擎
  12. Collection引擎与Laravel Scout

前提条件

要跟上进度,你应该具备以下条件

  • 在你的电脑上安装了 PHP 编译器。本教程使用PHP 8.1版本。
  • 在你的电脑上安装了Docker引擎或Docker桌面。
  • 一个Algolia云账户,你可以免费创建一个账户。

如何在一个Laravel项目中安装Scout

要使用Scout, 你必须首先创建一个Laravel应用程序,在那里你打算添加搜索功能。Laravel-Scout Bash脚本包含了在Docker容器中生成一个Laravel应用程序的命令。使用Docker意味着你不需要安装额外的支持软件, 如MySQL数据库.

Laravel-scout脚本使用Bash脚本语言, 所以你必须在Linux环境下执行它. 如果你运行的是Windows,确保你配置了Windows Subsystem for Linux(WSL)。

如果使用WSL,在你的终端中执行以下命令来设置你喜欢的Linux发行版。

wsl -s ubuntu

接下来, 导航到你的电脑上你想放置项目的位置. Laravel-Scout脚本会在这里生成一个项目目录。在下面的例子中, Laravel-Scout脚本会在desktop目录下创建一个目录.

cd /desktop

运行下面的命令来执行Laravel-Scout脚本。它将用必要的模板代码搭建一个Docker化的应用程序。

curl -s https://laravel.build/laravel-scout-app | bash

执行完毕后,用 cd laravel-scout-app 改变你的目录。然后,在项目文件夹中运行 sail-up 命令,为你的应用程序启动Docker容器。

Note:在许多Linux发行版上,你可能需要用 sudo 命令运行下面的命令来启动高权限。

./vendor/bin/sail up

你可能会遇到一个错误:

错误说明端口已被分配

错误说明端口已被分配

要解决这个问题,请使用APP_PORT变量,在 sail up 命令中指定一个端口:

APP_PORT=3001 ./vendor/bin/sail up

接下来,执行下面的命令,通过Artisan在PHP服务器上运行该应用程序。

php artisan serve

用Artisan为Laravel应用程序提供服务

用Artisan为Laravel应用程序提供服务

从你的网络浏览器, 导航到正在运行的应用程序,http://127.0.0.1:8000. 该应用程序将显示Laravel欢迎页面的默认路径。

Laravel应用程序的欢迎页面

Laravel应用程序的欢迎页面

如何添加Laravel Scout到应用程序中

在你的终端,输入命令来启用Composer PHP包管理器来添加Laravel Scout到项目中。

composer require laravel/scout

接下来,使用vendor:publish命令发布Scout配置文件。该命令将发布 scout.php 配置文件到你的应用程序的config目录。

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

现在,修改模板.env文件,使其包含一个 SCOUT_QUEUE 的布尔值。

SCOUT_QUEUE 值将允许Scout排队操作,提供更好的响应时间。没有它,像Meilisearch这样的Scout驱动不会立即反映新记录。

SCOUT_QUEUE=true

此外,修改.env文件中的 DB_HOST 变量,使其指向你的本地主机,以便在Docker容器中使用MySQL数据库

DB_HOST=127.0.0.1

如何标记一个模型和配置索引

Scout默认不会启用可搜索的数据模型。你必须使用 Laravel/Scout/Searchable 特性将模型明确标记为可搜索。

你将从为一个演示的 Train 应用程序创建一个数据模型开始,并将其标记为可搜索。

如何创建一个模型

对于 Train 应用程序,你要存储每个可用的火车的占位符名称。

执行下面的Artisan命令来生成迁移,并将其命名为 create_trains_table

php artisan make:migration create_trains_table

制作一个名为create_trains_table的迁移文件

制作一个名为create_trains_table的迁移文件

迁移将在一个文件中生成,该文件的名称结合了指定的名称和当前的时间戳。

打开位于database/migrations/目录中的迁移文件。

要添加一个标题列,请在第17行的 id() 列后面添加以下代码。该代码将添加一个标题列。

$table->string('title');

要应用迁移,请执行以下命令。

php artisan migrate

应用Artisan迁移

应用Artisan迁移

运行数据库迁移后, 在app/Models/目录下创建一个名为Train.php的文件.

如何添加LaravelScoutSearchable特性

通过添加 Laravel/Scout/Searchable 特性来标记 Train 模型的搜索功能,如下图所示。

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Train extends Model
{
use Searchable;
public $fillable = ['title'];

另外,你需要通过覆盖 searchable 方法来配置搜索索引。Scout的默认行为将持久化模型以匹配模型表的名称。

因此,在Train.php文件中,在上一个区块的代码下面添加以下代码。

/**
* Retrieve the index name for the model.
*
* @return string
*/
public function searchableAs()
{
return 'trains_index';
}
}

如何使用Algolia和Scout

对于Laravel Scout的第一次全文搜索,你将使用Algolia驱动。Algolia是一个软件即服务(SaaS)平台,用于搜索大量的数据。它为开发人员提供了一个网络仪表板来管理他们的搜索索引,以及一个强大的API,你可以通过你喜欢的编程语言的软件开发工具包(SDK)来访问。

在Laravel应用程序中,你将使用PHP的Algolia客户端包

如何设置Algolia

首先, 你必须为你的应用程序安装Algolia PHP搜索客户端包.

执行下面的命令.

composer require algolia/algoliasearch-client-php

接下来,你必须在.env文件中设置你的应用ID和Algolia的Secret API Key凭证。

使用你的网络浏览器,导航到你的Algolia仪表板,以获得应用ID和秘密API Key凭证。

点击左侧边栏底部的 “Settings“,进入Settings页面。

接下来,在Team and Access部分点击API Keys,查看你的Algolia账户的密钥。

Algolia Cloud上的API Keys页面

Algolia Cloud上的API Keys页面

在API Keys页面, 注意Application IDAdmin API Key值. 你将使用这些凭证来验证Laravel应用程序和Algolia之间的连接。

应用程序ID和管理员API密钥

应用程序ID和管理员API密钥

使用你的代码编辑器将下面的代码添加到你的.env文件中,并将占位符替换为相应的Algolia API秘密。

ALGOLIA_APP_ID=APPLICATION_ID
ALGOLIA_SECRET=ADMIN_API_KEY

另外,用下面的代码替换 SCOUT_DRIVER 变量,将其值从 meilisearch 改为 algolia 。改变这个值将指示Scout使用Algolia驱动。

SCOUT_DRIVER=algolia

如何创建应用程序的控制器

app/Http/Controllers/目录下,创建一个TrainSearchController.php文件,为应用程序存储一个控制器。该控制器将列出并添加数据到 Train 模型中。

TrainSearchController.php文件中添加以下代码块来建立控制器。

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Models\Train;
class TrainSearchController extends Controller
{
/**
* Get the index name for the model.
*
* @return string
*/
public function index(Request $request)
{
if($request->has('titlesearch')){
$trains = Train::search($request->titlesearch)
->paginate(6);
}else{
$trains = Train::paginate(6);
}
return view('Train-search',compact('trains'));
}
/**
* Get the index name for the model.
*
* @return string
*/
public function create(Request $request)
{
$this->validate($request,['title'=>'required']);
$trains = Train::create($request->all());
return back();
}
}

如何创建应用程序的路由

在这一步,你将创建列出和添加新列车到数据库的路线。

打开你的routes/web.php文件,用下面的代码块替换现有代码。

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TrainSearchController;
Route::get('/', function () {
return view('welcome');
});
Route::get('trains-lists', [TrainSearchController::class, 'index']) -> name ('trains-lists');
Route::post('create-item', [TrainSearchController::class, 'create']) -> name ('create-item');

上面的代码在应用程序中定义了两条路线。 /trains-lists 路由的 GET 请求列出所有存储的火车数据。 /create-item 路由的 POST 请求创建新的列车数据。

如何创建应用程序的视图

resources/views/目录下创建一个文件,命名为Train-search.blade.php。该文件将显示搜索功能的用户界面。

将下面的代码块内容添加到Train-search.blade.php文件中,为搜索功能创建一个单页。

<!DOCTYPE html>
<html>
<head>
<title>Laravel - Laravel Scout Algolia Search Example</title>
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2 class="text-bold">Laravel Full-Text Search Using Scout </h2><br/>
<form method="POST" action="{{ route('create-item') }}" autocomplete="off">
@if(count($errors))
<div class="alert alert-danger">
<strong>Whoops!</strong> There is an error with your input.
<br/>
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="row">
<div class="col-md-6">
<div class="form-group {{ $errors->has('title') ? 'has-error' : '' }}">
<input type="text" id="title" name="title" class="form-control" placeholder="Enter Title" value="{{ old('title') }}">
<span class="text-danger">{{ $errors->first('title') }}</span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<button class="btn btn-primary">Create New Train</button>
</div>
</div>
</div>
</form>
<div class="panel panel-primary">
<div class="panel-heading">Train Management</div>
<div class="panel-body">
<form method="GET" action="{{ route('trains-lists') }}">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input type="text" name="titlesearch" class="form-control" placeholder="Enter Title For Search" value="{{ old('titlesearch') }}">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<button class="btn btn-primary">Search</button>
</div>
</div>
</div>
</form>
<table class="table">
<thead>
<th>Id</th>
<th>Train Title</th>
<th>Creation Date</th>
<th>Updated Date</th>
</thead>
<tbody>
@if($trains->count())
@foreach($trains as $key => $item)
<tr>
<td>{{ ++$key }}</td>
<td>{{ $item->title }}</td>
<td>{{ $item->created_at }}</td>
<td>{{ $item->updated_at }}</td>
</tr>
@endforeach
@else
<tr>
<td colspan="4">No train data available</td>
</tr>
@endif
</tbody>
</table>
{{ $trains->links() }}
</div>
</div>
</div>
</body>
</html>

上面的HTML代码包含一个表单元素,其中有一个输入字段和一个按钮,用于在将列车保存到数据库之前输入列车的标题。该代码还包含一个HTML表格,显示数据库中的列车条目的idtitlecreated_at, 和 updated_at等详细信息。

要查看该页面,请从你的网络浏览器中导航到http://127.0.0.1:8000/trains-lists。

Train模型数据

Train模型数据

目前数据库是空的,所以您需要在输入栏中输入一个演示train的标题,然后点击Create New Train来保存。

插入一个新的train条目

插入一个新的train条目

要使用搜索功能,从任何已保存的列车标题中键入一个关键词到Enter Title For Search的输入字段中,然后点击Search

如下图所示,只有标题中包含关键词的搜索条目才会显示。

使用搜索功能查找train条目

使用搜索功能查找train条目

使用Laravel Scout的Meilisearch

Meilisearch是一个开源的搜索引擎,专注于速度、性能和改进的开发者体验。它与Algolia共享一些功能,使用相同的算法、数据结构和研究 – 但使用不同的编程语言

开发人员可以在他们的企业内部或云基础设施中创建和自我托管Meilisearch实例。Meilisearch也有一个类似于Algolia的测试版云产品,供那些想使用该产品而不管理其基础设施的开发者使用。

在本教程中, 你已经有一个本地的Meilisearch实例在你的Docker容器中运行. 现在你将扩展Laravel Scout的功能来使用Meilisearch实例.

要添加Meilisearch到Laravel应用程序, 在你的项目终端运行以下命令.

composer require meilisearch/meilisearch-php

接下来, 你需要修改.env文件中的Meilisearch变量来配置它.

将.env文件中的 SCOUT_DRIVERMEILISEARCH_HOST, 和 MEILISEARCH_KEY 变量替换为以下内容。

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

SCOUT_DRIVER 键指定Scout应该使用的驱动程序,而 MEILISEARCH_HOST 代表你的Meilisearch实例运行的域。尽管在开发过程中不需要,但建议在生产中添加 MEILISEARCH_KEY

注意:当使用Meilisearch作为你的首选驱动时,注释掉Algolia ID和Secret。

在完成了.env的配置后,你应该使用下面的Artisan命令来索引你先前存在的记录。

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

Laravel Scout与数据库引擎

Scout的数据库引擎可能最适合于使用较小的数据库或管理不密集的工作负载的应用程序。目前, 该数据库引擎支持PostgreSQL和MySQL.

这个引擎使用 “where-like” 条款和针对你现有数据库的全文索引,使它能够找到最相关的搜索结果。在使用数据库引擎时,你不需要对你的记录进行索引。

要使用数据库引擎,你必须将你的 SCOUT_DRIVER .env变量设置为数据库。

打开Laravel应用程序中的.env文件,改变SCOUT_DRIVER变量的值。

SCOUT_DRIVER = database

将你的驱动程序改为数据库后, Scout将切换到使用数据库引擎进行全文搜索.

Collection引擎与Laravel Scout

除了数据库引擎之外,Scout还提供了一个集合引擎。这个引擎使用 “where” 条款和集合过滤来提取最相关的搜索结果。

与数据库引擎不同, 收集引擎支持所有Laravel也支持的关系型数据库.

你可以通过将 SCOUT_DRIVER 环境变量设置为 collection ,或者在Scout配置文件中手动指定集合驱动来使用集合引擎。

SCOUT_DRIVER = collection

使用Elasticsearch进行资源管理

凭借Elasticsearch查询的优势,Explorer是Laravel Scout的一个现代Elasticsearch驱动。它提供了一个兼容的Scout驱动和好处,如实时存储,搜索和分析大量的数据。使用Laravel的Elasticsearch可以在几毫秒内提供结果。

要在你的Laravel应用程序中使用Elasticsearch Explorer驱动,你需要配置Laravel-Scout脚本生成的boilerplate模板docker-compose.yml文件。你将添加Elasticsearch的额外配置,并重新启动容器。

打开你的docker-compose.yml文件,将其内容替换为以下内容。

# For more information: https://laravel.com/docs/sail
version: '3'
services:
laravel.test:
build:
context: ./vendor/laravel/sail/runtimes/8.1
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
image: sail-8.1/app
extra_hosts:
- 'host.docker.internal:host-gateway'
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
volumes:
- '.:/var/www/html'
networks:
- sail
depends_on:
- mysql
- redis
- meilisearch
- mailhog
- selenium
- pgsql
- elasticsearch
mysql:
image: 'mysql/mysql-server:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: "%"
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- 'sail-mysql:/var/lib/mysql'
- './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
networks:
- sail
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
retries: 3
timeout: 5s
elasticsearch:
image: 'elasticsearch:7.13.4'
environment:
- discovery.type=single-node
ports:
- '9200:9200'
- '9300:9300'
volumes:
- 'sailelasticsearch:/usr/share/elasticsearch/data'
networks:
- sail
kibana:
image: 'kibana:7.13.4'
environment:
- elasticsearch.hosts=http://elasticsearch:9200
ports:
- '5601:5601'
networks:
- sail
depends_on:
- elasticsearch
redis:
image: 'redis:alpine'
ports:
- '${FORWARD_REDIS_PORT:-6379}:6379'
volumes:
- 'sail-redis:/data'
networks:
- sail
healthcheck:
test: ["CMD", "redis-cli", "ping"]
retries: 3
timeout: 5s
pgsql:
image: 'postgres:13'
ports:
- '${FORWARD_DB_PORT:-5432}:5432'
environment:
PGPASSWORD: '${DB_PASSWORD:-secret}'
POSTGRES_DB: '${DB_DATABASE}'
POSTGRES_USER: '${DB_USERNAME}'
POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
volumes:
- 'sailpgsql:/var/lib/postgresql/data'
networks:
- sail
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"]
retries: 3
timeout: 5s
meilisearch:
image: 'getmeili/meilisearch:latest'
ports:
- '${FORWARD_MEILISEARCH_PORT:-7700}:7700'
volumes:
- 'sail-meilisearch:/meili_data'
networks:
- sail
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--spider",  "http://localhost:7700/health"]
retries: 3
timeout: 5s
mailhog:
image: 'mailhog/mailhog:latest'
ports:
- '${FORWARD_MAILHOG_PORT:-1025}:1025'
- '${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025'
networks:
- sail
selenium:
image: 'selenium/standalone-chrome'
extra_hosts:
- 'host.docker.internal:host-gateway'
volumes:
- '/dev/shm:/dev/shm'
networks:
- sail
networks:
sail:
driver: bridge
volumes:
sail-mysql:
driver: local
sail-redis:
driver: local
sail-meilisearch:
driver: local
sailpgsql:
driver: local
sailelasticsearch:
driver: local

接下来,运行下面的命令,提取你添加到docker-compose.yml文件中的新Elasticsearch镜像。

docker-compose up

然后,执行下面的Composer命令,将资源管理器安装到项目中。

composer require jeroen-g/explorer

你还需要为资源管理器驱动程序创建一个配置文件。

执行下面的Artisan命令,生成一个explorer.config文件用于存储配置。

php artisan vendor:publish --tag=explorer.config

上面生成的配置文件将在/config目录下可用。

在你的config/explorer.php文件中,你可以使用 indexes 键引用你的模型。

'indexes' => [
\App\Models\Train::class
],

.env文件中的 SCOUT_DRIVER 变量的值改为 elastic ,以配置Scout使用资源管理器驱动程序。

SCOUT_DRIVER = elastic

在这一点上,你将通过实现Explorer接口和重写 mappableAs() 方法在 Train 模型中使用Explorer。

打开App > Models目录下的Train.php文件,用下面的代码替换现有代码。

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
class Train extends Model implements Explored
{
use HasFactory;
use Searchable;
protected $fillable = ['title'];
public function mappableAs(): array
{
return [
'id'=>$this->Id,
'title' => $this->title,
];
}
}

有了上面添加的代码,你现在可以使用资源管理器来搜索 Train 模型中的文本。

小结

对于PHP开发者来说, Laravel和像Scout这样的附加组件使得整合快速,强大的全文搜索功能变得轻而易举。有了数据库引擎, 采集引擎, 以及Meilisearch和Elasticsearch的功能, 你可以与你的应用程序的数据库互动, 并在几毫秒内实现高级搜索机制.

无缝地管理和更新你的数据库意味着你的用户获得最佳的体验,同时你的代码保持干净和高效。

评论留言