Winter / wn-search-plugin
Winter CMS 的搜索插件
Requires
- php: ^8.0
- composer/installers: ~1.0
- laravel/scout: ^9.4.5
- teamtnt/tntsearch: ^4.0
Suggests
- algolia/algoliasearch-client-php: Required to use the Algolia engine (^3.2).
- meilisearch/meilisearch-php: Required to use the MeiliSearch engine (^0.23).
README
为 Winter 添加全文搜索功能,基于 Laravel Scout 的基础构建。该插件主要作为 Laravel Scout 的包装器,并在 Winter 的架构内提供其所有功能集,同时还包括一些额外的功能,以使其在 Winter 中使用更加便捷。
需求
- PHP 8.0 或更高版本
- Winter CMS 1.2.0 或更高版本(由于 Laravel 9 的需求)
入门指南
要安装插件,您可以通过 Winter CMS 市场place 进行安装,或者您可以使用 Composer 安装。
composer require winter/wn-search-plugin
然后,运行迁移以确保插件启用
php artisan winter:up
配置
此插件的配置主要通过 search.php
配置文件进行。您可以通过运行以下命令将此配置发布到项目的 config
目录
php artisan vendor:publish --provider="Winter\Search\Plugin"
这将在 config/winter/search/search.php
创建您的配置文件,其中您可以覆盖所有默认配置值。
准备您的模型
作为包装器,您可以使用 Laravel Scout 提供的所有基本功能。与搜索插件的实现相比,只有几个细微的差异
- 配置值存储在
search
键中。无论何时提及scout
配置值,您都必须使用search
。 - 软删除模型通过使用
Winter\Storm\Database\Traits\SoftDelete
特性确定,而不是基于 Laravel 的基本SoftDeletes
特性。
要使特定的数据库模型可搜索,您只需将该模型的 Winter\Search\Behaviors\Searchable
行为添加到该模型即可。此行为将注册一个模型观察者,该观察者将自动同步模型记录到索引
<?php namespace Winter\Plugin\Models; use Model; class MyModel extends Model { public $implement = [ \Winter\Search\Behaviors\Searchable::class, ]; }
对于 Halcyon 模型,您必须使用 Winter\Search\Behaviors\Halcyon\Searchable
行为,以便正确地挂钩 Halcyon 提供的独特功能。
当模型创建、更新或删除时,索引将自动更新以反映该模型记录的状态。
配置可搜索数据
默认情况下,整个模型将转换为数组形式并持久化到搜索索引中。如果您希望限制存储在索引中的数据,您可以在模型中提供 $searchable
属性。此属性将代表您希望存储在索引中的所有模型属性
<?php namespace Winter\Plugin\Models; use Model; class Post extends Model { public $implement = [ \Winter\Search\Behaviors\Searchable::class, ]; public $searchable = [ 'title', 'summary' ]; }
如果您想要更多的数据控制,您可以覆盖 toSearchableArray
方法
<?php namespace Winter\Plugin\Models; use Model; class Post extends Model { public $implement = [ \Winter\Search\Behaviors\Searchable::class, ]; /** * Get the indexable data array for the model. * * @return array */ public function toSearchableArray() { $array = $this->toArray(); // Customize the data array... return $array; } }
将搜索添加到第三方模型
您可以通过使用 Winter 中的 动态类扩展 功能将搜索功能添加到第三方插件。这通常在 Plugin.php
注册文件中完成,通常位于 boot()
方法中。
以这种方式扩展模型时,您还可能需要指定希望包含在搜索索引中的可搜索数据,使用之前指定的$searchable
属性或toSearchableArray()
方法。
<?php namespace Winter\Plugin; class Plugin extends \System\Classes\PluginBase { public function boot() { \ThirdParty\Plugin\Models\Model::extend(function ($model) { $model->implement[] = \Winter\Search\Behaviors\Searchable::class; // Add a dynamic property to specify the searchable data $model->addDynamicProperty('searchable', [ 'id', 'title', 'description', ]); // Or, add a dynamic method instead. $model->addDynamicMethod('toSearchableArray', function () use ($model) { $array = $model->toArray(); // Customize the data array... return $array; }); }); } }
配置模型ID
通常,模型的键将作为存储在搜索索引中的模型唯一ID。如果您希望使用其他列作为模型的标识符,您可以覆盖getSearchKey
和getSearchKeyName
方法来自定义此行为。
<?php namespace Winter\Plugin\Models; use Model; class User extends Model { public $implement = [ \Winter\Search\Behaviors\Searchable::class, ]; /** * Get the value used to index the model. * * @return mixed */ public function getSearchKey() { return $this->email; } /** * Get the key name used to index the model. * * @return mixed */ public function getSearchKeyName() { return 'email'; } }
注意:某些搜索提供商(如Meilisearch)对ID允许的字符有限制。为了安全起见,我们建议您将ID限制为以下字符:
A-Z
、a-z
、0-9
、破折号和下划线。任何其他字符可能会阻止某些搜索提供商索引您的记录。
注册搜索处理器
一旦您的模型准备就绪,可以支持搜索功能,您可以注册一个搜索处理器,允许通过包含的组件搜索模型。
在您的Plugin.php
文件中通过指定返回数组的registerSearchHandlers
方法来注册搜索处理器。
<?php namespace Acme\Plugin; class Plugin extends \System\Classes\PluginBase { public function registerSearchHandlers() { return [ 'mySearch' => [ 'name' => 'My Search', 'model' => \Winter\Plugin\Models\Post::class, 'record' => [ 'title' => 'title', 'image' => 'featured_image', 'description' => 'description', 'url' => 'url', ] ] ]; } }
每个数组项应指定一个代表搜索处理器ID的关键字名称。以下属性可以作为处理器的一部分进行指定
记录处理器
每个搜索处理器也可以提供一个结果处理器,以精细调整您希望显示或过滤的结果。在最简单的情况下,记录处理器只需返回一个包含3个属性的数组,每个记录包含以下属性
title
:结果的标题。description
:结果的附加上下文。url
:结果将指向的URL。
它还可以可选地提供以下属性以提供更多上下文
group
:当使用分组结果时,此结果所属的组。label
:结果的标签,可能提供更多上下文或对结果进行分组。image
:结果对应图像的路径。
当然,您可以在数组中定义额外的属性。
记录处理器可以通过多种方式配置。
字段映射数组
您可以直接返回一个具有上述属性并映射到模型内对应字段的数组。
'record' => [ 'title' => 'title', 'image' => 'image', 'description' => 'description', 'url' => 'url', ]
具有回调的数组映射
类似于上述方法,您还可以指定一些或所有属性使用回调方法,该方法将接收两个参数:每个结果的结果实例和原始查询。
'record' => [ 'title' => 'title', 'image' => 'image', 'description' => function ($model, $query) { return substr($model->description, 0, 100) . '...'; }, 'url' => 'url', ]
回调方法
您还可以使整个处理器通过回调方法进行。这提供了最大的控制权,因为您还可以过滤掉记录。
回调方法应始终返回一个包含上述主要属性的数组,但您可以根据需要包含任何附加属性。
回调方法还可以返回false
以排除记录。
'record' => function ($model, $query) { if ($model->isNotPublished()) { return false; } return [ 'title' => $model->title, 'image' => $model->image->url(), 'description' => $model->getDescription(), 'url' => $model->getUrl(), ]; }
结果相关性
默认情况下,搜索插件中的结果没有按任何特定方式排序以考虑结果的相关性。虽然这对于使用具有自己的相关性算法(或可以配置为这样)的索引引擎(如Algolia或Meilisearch)的情况可能很好,但这可能会影响使用没有相关性系统(如数据库和集合索引引擎)的结果。
为了在这些引擎中支持一定级别的关系,可以在从索引检索结果后对结果进行后处理以分配相关性得分。此系统中的相关性由索引中$searchable
定义中属性名称的顺序确定。
例如,以下定义
public $searchable = [ 'title', 'description', 'keywords', ];
《title》字段将是相关性最高的字段,其次是《description》字段,然后是《keywords》字段。在《title》中的匹配比在《description》中的匹配权重更高,而《description》中的匹配权重又高于《keywords》中的匹配。
相关性评分还会考虑查询中使用的单词。例如,如果使用了查询《install winter cms》,则单词《install》的权重高于《winter》,而《winter》的权重又高于单词《cms》。
要启用结果相关性,您可以在任何搜索之后调用《getWithRelevance()`》或《firstRelevant()`》方法。
$results = \Acme\Blog\BlogSearch::doSearch('install winter cms')->getWithRelevance();
getWithRelevance()
将检索所有记录,并按相关性评分排序,而《firstRelevant()`》将只检索最相关的记录。
如果您想自定义相关性评分,也可以为这两种方法提供可调用的函数。这个可调用的函数必须接受两个参数,即模型实例和查询中的单词数组,并返回一个float
或int
类型的评分,该评分将用于按降序(评分越高,相关性越高)对结果进行排序。索引中找到的每个记录都将通过这个可调用的函数。
$results = \Acme\Blog\BlogSearch::doSearch('install winter cms')->getWithRelevance(function ($model, array $words) { // Score each record and return score as a integer or float. });