namoshek/laravel-scout-database

这是一个通用的Laravel Scout驱动,它使用SQL数据库作为存储后端,在索引模型数据上执行全文搜索。索引数据以规范化的形式存储,允许高效的搜索。

v2.4.0 2024-01-31 18:21 UTC

README

Latest Version on Packagist Total Downloads Tests Quality Gate Status Maintainability Rating Reliability Rating Security Rating Vulnerabilities License

本软件包提供了一种通用的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 文件中找到。配置选项在文件本身中有详细说明。默认情况下,该软件包使用 UnicodeTokenizerPorterStemmer,这两个都适合英文语言。搜索会在最后一个标记后添加一个尾随通配符,且不需要所有搜索词都存在才能使文档出现在结果中(但至少必须有一个匹配项)。

您还可以通过在配置文件中启用 wildcard_all_tokens 来在每个搜索标记中添加通配符,尽管出于性能原因不推荐这样做。

基本安装通常不需要您更改这些设置。只是为了确保,您应该查看 connection 选项。如果您想更改此选项,请在运行迁移之前进行更改,否则将使用错误的数据库连接创建表。

支持的标记化器

目前,只有 UnicodeTokenizer 是可用的。它会在任何既不是字母也不是数字的字符处拆分字符串,根据 \p{L}\p{N} 正则表达式模式。这意味着点、冒号、破折号、空白等都是拆分标准。

如果您对标记化器有不同的要求,您可以通过配置提供自己的实现。只需确保它实现了 Tokenizer 接口。

支持的词干提取器

目前,所有由 wamania/php-stemmer 软件包实现的词干提取器都是可用的。为每个词干提取器添加了一个包装类

如果您对词干提取器有不同的要求,您可以通过配置提供自己的实现。只需确保它实现了 Stemmer 接口。

用法

该软件包遵循官方 Scout 文档中描述的可用用例 official Scout documentation。但是请注意所列的 限制

它是如何工作的?

索引

搜索驱动程序内部使用一个包含术语和文档关联的单一表。当索引文档(即在搜索索引中添加或更新模型)时,引擎将使用配置的标记化器将每个列的输入拆分为标记。默认配置的标记化器简单地将输入拆分为由任何 Unicode 字母或数字组成的单词,这意味着任何其他字符如 ,.-_!?/、空白和所有其他特殊字符都被视为标记分隔符,将由标记化器移除。这样,这些字符永远不会出现在搜索索引中。

输入被分词后,每个标记(在这个阶段我们实际上期望标记是单词)都会通过配置的词干提取器来获取词干(即根词)。执行此操作允许我们在以后搜索类似词语。例如,PorterStemmer将针对输入的intelligentintelligence都输出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)。请参阅许可证文件以获取更多信息。