使用 Laravel 和 Typesense 构建快速、模糊的网站搜索
发布于 作者 Benjamen Pyle
现代应用程序对数据存储能力有很高的要求。在过去 10 年中,专门构建的数据平台的兴起得到了快速发展,围绕数据和分析、事务性、相关实体和图以及搜索和人工智能进行了细分。仅搜索领域就出现了巨大的增长,这要求供应商将他们的平台推向新的和新兴领域,包括支持向量嵌入。所有这些听起来都很棒而且很未来,但如果同一个平台既支持 AI 又支持传统搜索怎么办?那么如何支持更人性化的搜索,包括拼写错误等情况呢?我想探索由 Typesense 公司提供的平台,以及它与 PHP 和 Laravel 在网络领域的结合情况。
为了披露,在我开始之前,这篇文章由 Typesense 赞助,Typesense 是“闪电般快速的开源搜索”背后的公司。我正在写一系列文章,探讨构建者如何利用他们的搜索产品与各种语言和设置。
设计概述
在深入代码并介绍如何开始使用 Typesense 和 Laravel 之前,我想暂停一下,强调我所构建的内容。提前这样做将使内容围绕结果进行定位。
从这个图中,我将逐步介绍
- 突出显示项目概述
- 创建将作为数据基础的
Todo
模型 - 演示为 Typesense 配置 Laravel 的 Scout
- 说明从数据库到 Typesense 的模型同步
- 展示构建 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 将使用类型为 string
的 id
字段作为该文档的键,如 本文档中所述。除了将 id
转换为 string
之外,还建议将时间戳存储为 Unix 时间戳,使其成为一个整数。
<?phpnamespace 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 是一位技术主管和软件开发人员,拥有超过 20 年的创业公司和大企业经验。他是 Pyle Cloud Technologies 的联合创始人兼首席执行官。