使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

发布于 作者

Building fast, fuzzy site search with Laravel and Typesense image

现代应用程序对数据存储能力有很高的要求。在过去 10 年中,专门构建的数据平台的兴起得到了快速发展,围绕数据和分析、事务性、相关实体和图以及搜索和人工智能进行了细分。仅搜索领域就出现了巨大的增长,这要求供应商将他们的平台推向新的和新兴领域,包括支持向量嵌入。所有这些听起来都很棒而且很未来,但如果同一个平台既支持 AI 又支持传统搜索怎么办?那么如何支持更人性化的搜索,包括拼写错误等情况呢?我想探索由 Typesense 公司提供的平台,以及它与 PHP 和 Laravel 在网络领域的结合情况。

为了披露,在我开始之前,这篇文章由 Typesense 赞助,Typesense 是“闪电般快速的开源搜索”背后的公司。我正在写一系列文章,探讨构建者如何利用他们的搜索产品与各种语言和设置。

设计概述

在深入代码并介绍如何开始使用 Typesense 和 Laravel 之前,我想暂停一下,强调我所构建的内容。提前这样做将使内容围绕结果进行定位。

从这个图中,我将逐步介绍

  1. 突出显示项目概述
  2. 创建将作为数据基础的 Todo 模型
  3. 演示为 Typesense 配置 Laravel 的 Scout
  4. 说明从数据库到 Typesense 的模型同步
  5. 展示构建 Laravel 控制器和视图以支持模型

项目构建

以下段落的依据是这个 GitHub 仓库。为了完全披露,我的 PHP 经验可以追溯到很多年前,包含的代码需要一些更新才能“投入生产”,但真正令人感兴趣的是 Typesense 组件以及 Laravel 如何让数据库和 Typesense 存储库保持同步变得如此简单。

入门

克隆项目后,你会发现它是一个基本的、基于模板的 Laravel 设置,它是从运行 composer create-project laravel/laravel typesense-app 创建的。从 Typesense 的角度来看,有趣的部分从我打开 app/Models/Todo.php 中的文件时开始出现。

Todo 模型

我的 Todo 模型是经典的案例,它需要一些有意义的东西来存储,但这些东西并不那么具体,以至于难以关联或理解。与 Typesense 特别相关的是包含 use Searchable,它是一个模型特征。通过包含此特征,将注册一个模型观察者,它会自动将模型与数据库和 Typesense 同步。令人难以置信的是,只需要一行代码,我就可以获得所有这些功能。

Todo 模型的第二个与 Typesense 相关的部分是 toSearchableArray() 函数。默认情况下,Typesense 将使用类型为 stringid 字段作为该文档的键,如 本文档中所述。除了将 id 转换为 string 之外,还建议将时间戳存储为 Unix 时间戳,使其成为一个整数。

<?php
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
 
class Todo extends Model
{
use HasFactory;
use Searchable;
 
protected $fillable = [
'name',
'description'
];
 
public function toSearchableArray()
{
return array_merge($this->toArray(),[
'id' => (string) $this->id,
'created_at' => $this->created_at->timestamp,
]);
}
}

配置 Scout

定义并准备好存储模型后,我需要配置 Scout,以便观察者知道将数据存储在何处。scout.php 文件位于项目 config 目录中,如下图所示。

Scout 配置文件本质上是一个包含不同存储平台和驱动程序对象的巨大数组。这使得添加 Typesense 配置非常容易维护。Typesense 配置的两个独立部分需要进行处理。

第一部分侧重于客户端配置。在本部分中,我设置 API 密钥、主机、端口和路径等内容。把它想象成设置的驱动程序部分。从这个角度来看,它感觉像一个普通的数据库配置。

'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', '<your-api-key>'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
'nearest_node' => [
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
],
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),

第二个需要处理的部分更多的是实体集合。对于我想存储和与 Typesense 同步的每个类,我需要配置这些映射。对于我的 Todo 模型,这些映射包括我希望在文档中包含的每个字段。这包括字段名称和该字段的数据类型。此外,我还可以在 default_sorting 中进行配置,并为我希望通过 search-parameters 搜索的特定字段设置索引。

'model-settings' => [
Todo::class => [
'collection-schema' => [
'fields' => [
[
'name' => 'id',
'type' => 'string',
],
[
'name' => 'name',
'type' => 'string',
],
[
'name' => 'description',
'type' => 'string',
],
[
'name' => 'created_at',
'type' => 'int64',
],
],
'default_sorting_field' => 'created_at',
],
'search-parameters' => [
'query_by' => 'name'
],
],
],

视图和控制器

现在我已经为观察者配置了 Todo 模型,并且已经调整了 Scout 来映射文档,是时候将所有这些与 Laravel 视图和控制器结合在一起了。

在我的 web.php 路由定义中,我正在建立以下端点。

  • /todos 从 Typesense 返回 Todos 列表
  • /todos/new 返回带有新建 Todo 表单的视图
  • /todos/save 将新 Todo 从表单写入数据库
  • /todos/search 对 Typesense 执行查询
Route::get("/todos", [TodoController::class, 'index']);
Route::get('/todos/new', [TodoController::class, 'newTodo']);
Route::post('/todos/save', [TodoController::class, 'store']);
Route::post('/todos/search', [TodoController::class, 'search']);

Todos 列表

在我的 Todo 列表中,我可以从 SQL 数据库中获取数据,但我选择从 Typesense 获取数据。我更愿意从搜索数据库中返回数据,以便在使用网格时拥有一致的数据和行为。控制器中索引处理程序内部的代码执行查询,然后将数据形成 Todo 模型。

$array = Todo::search('')->get()->toArray();
$todos = [];
 
foreach ($array as $todo) {
$t = new Todo;
$t->id = $todo['id'];
$t->name = $todo['name'];
$t->description = $todo['description'];
$t->created_at = $todo['created_at'];
$t->updated_at = $todo['updated_at'];
array_push($todos, $t);
}
 
return view('todo')->with( ['todos' => $todos] );;

在视觉上,它在我的网格中呈现出来,列与 Todo 上的字段匹配。

创建新的 Todo

我网格顶部的链接将带我到新建 Todo 表单。正如我的模型所概述的那样,我有一个名称和描述,因此在表格中所需的内容非常简单。使用 Laravel 太好太干净了,我的模型包括用于处理数据库的 save() 方法。

$todo = new Todo;
$todo->name = $request->name;
$todo->description = $request->description;
$todo->save();
 
return redirect('/todos')->with('status', 'Todo Data Has Been inserted');

搜索 Todos

使用我新创建的 Todo,我可以通过网格顶部的输入字段运行我的搜索。就像在 index 中一样,我将执行搜索,但这一次,我将使用表单输入中的值。

我真的很感谢使用 Scout 和 Typesense,因为作为一名开发者,它基本上被抽象掉了。我可以专注于我的用户体验,而无需担心一些低级细节。

public function search(Request $request): View
{
$search = '';
if ($request->search) {
$search = $request->search;
}
 
$array = Todo::search($search)->get()->toArray();
$searched = [];
 
foreach ($array as $todo) {
$t = new Todo;
$t->id = $todo['id'];
$t->name = $todo['name'];
$t->description = $todo['description'];
$t->created_at = $todo['created_at'];
$t->updated_at = $todo['updated_at'];
array_push($searched, $t);
}
 
return view('todo')->with( ['todos' => $searched ]);
}

总结

构建了视图和控制器后,我现在有了可以执行以下功能的工作解决方案。

  • 列出 Typesense 中的 Todos
  • 允许通过表单字段中的查询搜索 Typesense
  • HTML 表单创建新的 Todo 项目
  • 一个 Laravel 控制器将数据持久化到 SQLite 数据库中。
  • Laravel 会自动将新保存的数据同步到 Typesense。

通过使用 Laravel 框架与 Typesense 集成,我们获得了比实际代码编写更大的功能和更多的配置选项。随着项目中更多模型的添加,我们拥有可重复和可扩展的模式,可以将这些模型包含在搜索中,如果需要的话。

印象和收获

搜索是所有用户都会要求的功能,但它通常被实现为一系列针对数据库中列的 `like '%<string>%'` 类型语句。这在某些情况下可能有用,但会给开发人员带来沉重的负担,要求他们覆盖所有场景,并且还会给数据库带来沉重的负担,因为它需要不断满足需求。它还迫使开发人员与数据库管理员合作,设置诸如全文索引之类的功能,而这些功能并非普遍支持。

这就是 Typesense 和 Laravel 大放异彩的地方。Typesense 是一个专门的搜索数据库,它开箱即用地处理了一些很好的默认情况。例如,它会考虑打字错误。文档构建和索引通过简单的配置进行管理。然后,当与 Laravel 和 Scout 配合使用时,同步使使用这个平台变得轻而易举。

这仅仅是开始。

感谢您的阅读,祝您开发愉快!

Benjamen Pyle photo

Benjamen Pyle 是一位技术主管和软件开发人员,拥有超过 20 年的创业公司和大企业经验。他是 Pyle Cloud Technologies 的联合创始人兼首席执行官。

分类
Cube

Laravel 新闻

加入 40,000 多名其他开发人员,绝不错过新的提示、教程等等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成功能和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

无妥协

Joel 和 Aaron,两位来自 No Compromises 播客的经验丰富的开发人员,现在可以为您雇佣到您的 Laravel 项目中。 ⬧ 每月固定费用 7500 美元。 ⬧ 无需冗长的销售流程。 ⬧ 无需签订合同。 ⬧ 100% 退款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

运行旧的 Laravel 版本?即时、自动的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

为您的项目提供经验丰富的 Laravel 开发人员,拥有 4-6 年的经验,每月仅需 2500 美元。获得 160 小时的专享专业知识和无风险的 15 天试用期。立即预约通话!

Bacancy
Lucky Media logo

Lucky Media

立即获得幸运 - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源包,将现代无头电子商务功能的强大功能引入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 职位招聘平台

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动工具包

SaaSykit 是一个 Laravel SaaS 启动工具包,它包含运行现代 SaaS 所需的所有功能。支付、漂亮的结账、管理面板、用户仪表板、身份验证、即用型组件、统计信息、博客、文档等等。

SaaSykit: Laravel SaaS 启动工具包
Rector logo

Rector

您无缝升级 Laravel、降低成本和加快创新以帮助公司成功的合作伙伴

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。探索 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖性输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包向您的 Laravel 应用程序添加评论

阅读文章