namoshek / laravel-scout-database
这是一个通用的Laravel Scout驱动,它使用SQL数据库作为存储后端,在索引模型数据上执行全文搜索。索引数据以规范化的形式存储,允许高效的搜索。
Requires
- php: ^8.0
- ext-pdo: *
- illuminate/contracts: ^9.0|^10.0
- illuminate/database: ^9.0|^10.0
- illuminate/support: ^9.0|^10.0
- laravel/scout: ^9.0|^10.0
- staudenmeir/laravel-cte: ^1.0
- wamania/php-stemmer: ^2.0|^3.0
Requires (Dev)
- orchestra/testbench: ^7.0|^8.0
- squizlabs/php_codesniffer: ^3.5
README
本软件包提供了一种通用的Laravel Scout驱动,该驱动使用SQL数据库作为存储后端,对索引模型数据进行全文搜索。索引数据以规范化的形式存储,允许高效的模糊搜索,无需全匹配或精确匹配。
此驱动是 teamtnt/laravel-scout-tntsearch-driver
的替代品。主要区别在于,此驱动提供的功能较少(如地理搜索)。相反,它可与Laravel本身支持的(基本上是所有PDO驱动)的所有数据库系统一起工作。此外,搜索算法也略有不同。
所有测试都是通过GitHub Actions在PHP 8.0、8.1和8.2以及以下数据库系统上运行的
- SQLite 3
- MySQL 8.0
- PostgreSQL 13.1
- SQL Server 2017
关于支持数据库系统的实际限制主要与使用 公共表表达式 相关,使用 staudenmeir/laravel-cte。请在使用本软件包之前确保您的数据库系统受支持,否则您可能会遇到数据库错误。
安装
您可以通过composer安装此软件包
composer require namoshek/laravel-scout-database
安装软件包后,需要发布配置文件以及迁移
php artisan vendor:publish --provider="Namoshek\Scout\Database\ScoutDatabaseServiceProvider" --tag="config" php artisan vendor:publish --provider="Namoshek\Scout\Database\ScoutDatabaseServiceProvider" --tag="migrations"
如果您想使用与该软件包创建的表默认的前缀 scout_
不同的前缀,应相应地更改配置以及复制的迁移。完成这些操作后,您可以应用数据库迁移
php artisan migrate
从 v0.x
升级到 v1.x
迁移
新版本中,数据库模式已更改,需要使用以下命令发布新迁移
php artisan vendor:publish --provider="Namoshek\Scout\Database\ScoutDatabaseServiceProvider" --tag="migrations"
在“安装”部分中提到的相同提示也适用于新发布的迁移。
配置
配置已略有减少,您可能想将新配置文件与旧配置文件进行比较,以删除过时的设置。尽管跳过这部分不会对Scout驱动器的性能产生负面影响。
命令
已删除 \Namoshek\Scout\Database\Commands\CleanWordsTable::class
命令,如果您之前已添加,请取消安排它。
值得注意
大多数 protected
字段和方法都已更改为 private
,以简化未来向后兼容性中断更改的开发。如果您没有积极覆盖实现的一部分,这不会对您产生任何影响。
配置
为了指示Scout使用本软件包提供的驱动,您需要将 config/scout.php
中的 driver
选项更改为 database
。如果您未更改Scout配置文件,也可以将 SCOUT_DRIVER
环境变量设置为 database
。
该软件包本身的所有可用配置选项均可在 config/scout-database.php
文件中找到。配置选项在文件本身中有详细说明。默认情况下,该软件包使用 UnicodeTokenizer
和 PorterStemmer
,这两个都适合英文语言。搜索会在最后一个标记后添加一个尾随通配符,且不需要所有搜索词都存在才能使文档出现在结果中(但至少必须有一个匹配项)。
您还可以通过在配置文件中启用 wildcard_all_tokens
来在每个搜索标记中添加通配符,尽管出于性能原因不推荐这样做。
基本安装通常不需要您更改这些设置。只是为了确保,您应该查看 connection
选项。如果您想更改此选项,请在运行迁移之前进行更改,否则将使用错误的数据库连接创建表。
支持的标记化器
目前,只有 UnicodeTokenizer
是可用的。它会在任何既不是字母也不是数字的字符处拆分字符串,根据 \p{L}
和 \p{N}
正则表达式模式。这意味着点、冒号、破折号、空白等都是拆分标准。
如果您对标记化器有不同的要求,您可以通过配置提供自己的实现。只需确保它实现了 Tokenizer
接口。
支持的词干提取器
目前,所有由 wamania/php-stemmer
软件包实现的词干提取器都是可用的。为每个词干提取器添加了一个包装类
DanishStemmer
DutchStemmer
EnglishStemmer
FrenchStemmer
GermanStemmer
ItalianStemmer
NorwegianStemmer
NullStemmer
(可用于禁用词干提取)PorterStemmer
(默认,与EnglishStemmer
相同)PortugueseStemmer
RomanianStemmer
RussianStemmer
SpanishStemmer
SwedishStemmer
如果您对词干提取器有不同的要求,您可以通过配置提供自己的实现。只需确保它实现了 Stemmer
接口。
用法
该软件包遵循官方 Scout 文档中描述的可用用例 official Scout documentation。但是请注意所列的 限制。
它是如何工作的?
索引
搜索驱动程序内部使用一个包含术语和文档关联的单一表。当索引文档(即在搜索索引中添加或更新模型)时,引擎将使用配置的标记化器将每个列的输入拆分为标记。默认配置的标记化器简单地将输入拆分为由任何 Unicode 字母或数字组成的单词,这意味着任何其他字符如 ,
、.
、-
、_
、!
、?
、/
、空白和所有其他特殊字符都被视为标记分隔符,将由标记化器移除。这样,这些字符永远不会出现在搜索索引中。
输入被分词后,每个标记(在这个阶段我们实际上期望标记是单词)都会通过配置的词干提取器来获取词干(即根词)。执行此操作允许我们在以后搜索类似词语。例如,PorterStemmer
将针对输入的intelligent
和intelligence
都输出intellig
。这在接下来的搜索中会变得很清晰。
最后,这个过程的结果存储在数据库中。索引表填充了词干处理的结果和索引模型的关联(模型类型和标识符)。除此之外,对于索引表中的每一行,数据库还包含文档中出现的次数。我们使用这些信息在我们的搜索引擎的搜索部分进行评分。
搜索
当执行搜索查询时,与索引时使用相同的分词和词干提取过程应用于搜索查询字符串。这个过程的结果是一个词干(或根词)列表,然后用于执行实际的搜索。根据包的配置,搜索将返回包含至少一个或所有词干的文档。这是通过根据逆文档频率(即索引文档与包含搜索词之一的文档的比例)、词频(即搜索词在文档中的出现次数)和词偏差(仅对通配符搜索相关)为索引中的每个匹配项计算分数来实现的。返回的文档按分数降序排列,直到达到所需的限制。
扩展搜索索引
可以扩展搜索索引表(scout_index
)以包含自定义列。在索引过程中,这些列可以填充自定义内容,在搜索过程中可以针对这些列进行搜索(精确匹配)。此功能特别适用于使用多个租户的多租户应用程序,其中搜索索引由多个租户使用。
示例迁移
在我们的示例中,我们向搜索索引添加一个必填的tenant_id
列。
return new class extends Migration { public function up(): void { Schema::table('scout_index', function (Blueprint $table) { $table->uuid('tenant_id'); }); } public function down(): void { Schema::table('scout_index', function (Blueprint $table) { $table->dropColumn(['tenant_id']); }); } };
索引示例
在索引每个模型时添加tenant_id
class User extends Model { public function toSearchableArray(): array { return [ 'id' => $this->id, 'name' => $this->name, 'tenant_id' => new StandaloneField($this->tenant_id), ]; } }
搜索示例
在搜索时根据$tenantId
过滤tenant_id
,例如可以从HTTP请求中获取
User::search('Max Mustermann') ->where('tenant_id', $tenantId) ->get();
限制
显然,这个包并不提供像Elasticsearch这样的专业搜索引擎所提供的性能和质量。这个解决方案旨在用于需要相对简单设置的小到中等规模的项目。
还值得注意的是,以下Scout功能目前尚未实现
- 软删除
- 使用
User::search('Mustermann')->within('users_without_admins')
搜索自定义索引 - 使用
User::search('Musterfrau')->orderBy('age', 'desc')
进行自定义排序搜索- 实现此功能将很难与评分算法结合使用。只能对数据库查询的结果进行排序,这可能导致分页问题。
已知问题
这个搜索引擎的一个问题是,如果多个队列工作员同时索引单个文档,可能会导致问题(数据库将发生死锁)。为了避免这个问题,可以配置用于事务的尝试次数的数量。默认情况下,如果发生死锁(或任何其他错误),每个事务最多尝试三次。
许可证
MIT许可证(MIT)。请参阅许可证文件以获取更多信息。