hilsonxhero/elasticvision

此包的最新版本(2.0.1)没有可用的许可证信息。

Laravel Scout 的 Elasticsearch 驱动程序。

2.0.1 2023-03-31 11:14 UTC

This package is auto-updated.

Last update: 2024-09-30 01:27:14 UTC


README

Latest Version on Packagist

Elasticsearch 驱动程序,结合了 Laravel Scout 和 Elasticsearch 查询的力量。

安装

通过 Composer

composer require hilsonxhero/elasticvision

您需要配置文件来定义您的索引

php artisan vendor:publish --tag=elasticvision-config

同时,请不要忘记遵循Laravel Scout 的安装说明,并在您的 Laravel Scout 配置中,将驱动程序设置为 elastic

配置

您可以在 elasticvision 配置文件中定义您索引的映射

return [
    'indexes' => [
        \App\Models\Product::class,
    ],
];
php artisan scout:index products
php artisan scout:import "\App\Models\Product"

用法

在最后一种情况下,您可以实现 Explored 接口,并使用 mappableAs() 函数覆盖映射。

📕 文档

这意味着它取决于您是否喜欢在模型中将它们全部放在一起,还是在配置文件中分别存放。

词项查询

返回包含提供字段中确切词项的文档。

您可以使用词项查询根据精确值(如价格、产品 ID 或用户名)查找文档。

use App\Models\Post;

$posts = Post::search('lorem')
    ->filter(new Term('published', true))
    ->get();

词项查询

返回包含提供字段中一个或多个确切词项的文档。

词项查询与词项查询相同,但您可以搜索多个值。文档将匹配如果它包含至少一个词项。要搜索包含多个匹配词项的文档。

use App\Models\Post;

$posts = Post::search('lorem')
    ->should(new Terms('tags', ['featured'], 2))
    ->get();

词项的高级参数

字段

(可选,对象) 您希望搜索的字段。

提升

(可选,浮点数) 用于减少或增加查询相关性的浮点数。默认为 1.0。

排序

默认情况下,您的搜索结果将根据 Elasticsearch 的得分进行排序。如果您想介入并影响排序,可以使用 Laravel Scout 的默认 orderBy() 函数。

use App\Models\Post;

$results = Post::search('Self-steering')
    ->orderBy('published_at', 'desc')
    ->get();

范围

返回包含提供范围内词项的文档。

use App\Models\User;

$results = User::search('fugiat')->must(new Range('age',['gte' => 18, 'lte' => 35]))->get();

正则表达式查询

返回包含匹配正则表达式的词项的文档。

正则表达式是通过使用占位符字符(称为运算符)在数据中匹配模式的方法。有关正则表达式查询支持的运算符列表,请参阅正则表达式语法

use App\Models\User;

$results = User::search('fugiat')->must(new RegExp('username', 'k.*y','ALL',false))->get();

正则表达式的高级参数

字段

(可选,对象) 您希望搜索的字段。

(必需,字符串) 您希望在提供的 <field> 中查找的词项的正则表达式。有关支持的运算符列表,请参阅正则表达式语法

标志

(可选,字符串) 启用正则表达式的可选运算符。有关有效值和更多信息,请参阅正则表达式语法

不区分大小写

(可选,布尔值) 当设置为 true 时,允许不区分大小写的正则表达式值与索引字段值匹配。默认为 false,这意味着匹配的大小写取决于底层字段的映射。

提升

(可选,浮点数) 用于减少或增加查询相关性的浮点数。默认为 1.0。

通配符查询

返回匹配通配符模式的词项的文档。

通配符运算符是一个占位符,可以匹配一个或多个字符。例如,* 通配符运算符可以匹配零个或多个字符。您可以将通配符运算符与其他字符结合,创建通配符模式。

use App\Models\User;

$users = User::search()
    ->should(new Wildcard('username','ki*y'))
    ->get();

匹配查询

返回与提供的文本、数字、日期或布尔值匹配的文档。在匹配之前会分析提供的文本。

匹配查询是执行全文搜索的标准查询,包括模糊匹配的选项。

use App\Models\Article;

$articles = Article::search()
    ->must(new Matching('title','ipsum'))
    ->get();

匹配短语前缀查询

返回包含提供文本单词的文档,单词的顺序与提供的一致。提供文本的最后一个术语被视为前缀,匹配以该术语开头的任何单词。

use App\Models\User;

$users = User::search()
    ->should(new MatchPhrasePrefix('message','quick brown f'))
    ->get();

匹配短语查询

match_phrase 查询分析文本,并将分析后的文本创建为 phrase 查询。例如

use App\Models\User;

$users = User::search()
    ->should(new MatchPhrase('message','this is a test'))
    ->get();

嵌套查询

包装另一个查询以搜索嵌套字段。

nested 查询将嵌套的字段对象作为单独的文档索引进行搜索。如果对象匹配搜索,则 nested 查询返回根父文档。

use App\Models\Product;

$products = Product::search()
    ->must(new Nested('category', new Term('category.id', 2)))
    ->get();
use App\Models\Product;

$search = Product::search("lorem");

// $feature_ids = array([4 => [1,2], 5 => [1,2]])

foreach (request()->feature_id as $key => $value) {
  $query = new BoolQuery();
  $query->must(new Term('features.feature_id', $key));
  $query->must(new Terms('features.feature_value_id', $value));
  $boolQuery->add('must', new Nested('features', $query));
}

 $search->newCompound($boolQuery);

 $products = $search->paginate(15);

短语查询匹配以可配置的“slop”(默认为 0)为上限的术语,顺序不限。转置术语的 slop 为 2。

分析器可以设置以控制哪个分析器将对文本执行分析过程。默认为字段显式映射定义,或默认搜索分析器,例如

索引设置

您将通过您的索引的 映射 执行大部分配置。但是,如果您想要定义更高级的 Elasticsearch 设置,例如 分析器分词器,则需要使用索引设置来完成。

请注意,每次您更改索引设置时,都需要 重新创建 索引。

要开始使用索引设置,我们将在 Post 模型中扩展一个 indexSettings 函数来设置分析器。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Hilsonxhero\ElasticVision\Application\Explored;
use Hilsonxhero\ElasticVision\Application\IndexSettings;
use Laravel\Scout\Searchable;

class Post extends Model implements Explored, IndexSettings
{
    use HasFactory;
    use Searchable;

    protected $fillable = ['title', 'published'];

    public function mappableAs(): array
    {
        return [
            'id' => 'keyword',
            'title' => 'text',
            'published' => 'boolean',
            'created_at' => 'date',
        ];
    }

    public function indexSettings(): array
    {
        return [
            'analysis' => [
                'analyzer' => [
                    'standard_lowercase' => [
                        'type' => 'custom',
                        'tokenizer' => 'standard',
                        'filter' => ['lowercase'],
                    ],
                ],
            ],
        ];
    }
}

文本分析

文本分析是作为 索引设置 部分设置的。

以下示例创建了一个同义词分析器,最终结果是当您搜索 'Vue' 时,您也会获得 'React' 的搜索结果。为了确保同义词匹配所有情况,还会运行 lowercase 过滤器。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Hilsonxhero\ElasticVision\Application\Explored;
use Hilsonxhero\ElasticVision\Application\IndexSettings;
use Hilsonxhero\ElasticVision\Domain\Analysis\Analysis;
use Hilsonxhero\ElasticVision\Domain\Analysis\Analyzer\StandardAnalyzer;
use Hilsonxhero\ElasticVision\Domain\Analysis\Filter\SynonymFilter;
use Laravel\Scout\Searchable;

class Post extends Model implements Explored, IndexSettings
{
    use HasFactory;
    use Searchable;

    protected $fillable = ['title', 'published'];

    public function mappableAs(): array
    {
        return [
            'id' => 'keyword',
            'title' => [
                'type' => 'text',
                'analyzer' => 'frameworks',
            ],
            'published' => 'boolean',
            'created_at' => 'date',
        ];
    }

    public function indexSettings(): array
    {
        $synonymFilter = new SynonymFilter();
        $synonymFilter->setSynonyms(['vue => react']);

        $synonymAnalyzer = new StandardAnalyzer('frameworks');
        $synonymAnalyzer->setFilters(['lowercase', $synonymFilter]);

        return (new Analysis())
            ->addAnalyzer($synonymAnalyzer)
            ->addFilter($synonymFilter)
            ->build();
    }
}

聚合

聚合是您的搜索查询的一部分,可以对数据进行汇总。您可以在 Elasticsearch 的官方文档中阅读更多有关聚合的信息 官方文档。目前,并非所有聚合类型都内置,但创建缺失的类型应该是可行的(并且对这些包的补充非常受欢迎)。

添加聚合可以使您的搜索查询更复杂。以下是从演示应用程序中的一个示例

$search = Cartographer::search();
$search->aggregation('places', new TermsAggregation('place'));

$results = $search->raw();
$aggregations = $results->aggregations();

这将返回一个数组,显示每个地点在 Elasticsearch 索引中出现的次数。

创建同义词过滤器和分析器非常简单,但请注意,对于 Elasticsearch 来说它们“昂贵”的运行。在转向同义词之前,看看是否可以使用通配符或模糊查询。

⚡ 高级查询

Laravel Scout 的文档指出:“目前不支持更高级的 'where' 子句”。除了标准的模糊术语搜索之外,只能进行简单的 ID 检查。

$categories = Category::search('lorem ipsum')->filter(new MatchPhrase('status', 'enable'))->take(15)->get();

ElasticVision 使用查询构建器扩展了您的可能性,可以编写更复杂的查询。

class Product extends Model implements Explored
{
    public function mappableAs(): array
    {
        return [
            'id' => 'keyword',
            'title_fa' => [
                'type' => 'text',
                'analyzer' => 'my_analyzer',
            ],
            'title_en' => [
                'type' => 'text',
            ],
            'status' => [
                'type' => 'text',
            ],
            'category' => 'nested',
            'features' => 'nested',
            'variants' => 'nested',
            'has_stock' => 'boolean',
        ];
    }


    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'status' => $this->status,
            'category' => $this->category,
            'features' => $this->features,
            'variants' => ProductVariantResource::collection($this->variants)->toArray(true),
            'has_stock' => $this->has_stock,
        ];
    }

    public function indexSettings(): array
    {
        return [
            "analysis" => [
                "analyzer" => [
                    "my_analyzer" => [
                        "type" => "custom",
                        "tokenizer" => "standard",
                        "filter" => ["lowercase", "my_filter"]
                    ]
                ],
                "filter" => [
                    "my_filter" => [
                        "type" => "ngram",
                        "min_gram" => 2,
                    ]
                ]
            ],
            "index" => [
                "max_ngram_diff" => 13
            ]
        ];
    }

    /**
     * Get the name of the index associated with the model.
     *
     * @return string
     */
    public function searchableAs()
    {
        return 'products';
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    public function variants()
    {
        return $this->hasMany(ProductVariant::class);
    }
        public function features()
    {
        return $this->hasMany(ProductFeature::class);
    }

    /**
     * check inventory of product variations
     *
     * @return \Illuminate\Database\Eloquent\Casts\Attribute
     */

    protected function hasStock(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => $this->variants()->sum('stock') > 0 ? true : false
        );
    }

}

例如,要获取所有帖子,

  • 启用了状态
  • 有一个 ID 为 2 的分类
  • 标题和描述中包含“ipsum”
  • 有任意功能

您可以执行此搜索查询

        $boolQuery = new BoolQuery();

        $search = Product::search("ipsum")
            ->field('title')
            ->field('description')
            ->filter(new MatchPhrase('status', 'enable'))
            ->must(new Nested('category', new MatchPhrase('category.id',2)));

        if (request()->filled('available_stock')) {
            $search->filter(new Term('has_stock', true));
        }

        // request feature_ids value

        // $feature_ids = array([4 => [1,2], 5 => [1,2]])

        if (request()->filled('feature_ids')) {
            foreach (request()->feature_ids as $key => $value) {
                $query = new BoolQuery();
                $query->must(new MatchPhrase('features.feature_id', $key));
                $query->must(new Terms('features.feature_value_id', $value));
                $boolQuery->add('must', new Nested('features', $query));
            }
        }

        if (request()->filled('max_price') && request()->filled('min_price')) {
            $boolQuery->add('must', new Nested('variants', new Range(
                'variants.selling_price',
                ['gte' => request()->min_price]
            )));
            $boolQuery->add('must', new Nested('variants', new Range(
                'variants.selling_price',
                ['lte' => request()->max_price]
            )));
            $boolQuery->add('must_not', new Nested('variants', new Range(
                'variants.selling_price',
                ['lt' => request()->min_price]
            )));
            $boolQuery->add('must_not', new Nested('variants', new Range(
                'variants.selling_price',
                ['gt' => request()->max_price]
            )));
        }

        $search->newCompound($boolQuery);

        $products = $search->paginate(15);

        return $products;

调试

有时您可能会想知道为什么某些结果被返回或未被返回。

以下是从ElasticVision演示应用程序中的示例,尽管不是复杂的查询

class SearchController
{
    public function __invoke(SearchFormRequest $request)
    {
        $people = Cartographer::search($request->get('keywords'))->get();

        return view('search', [
            'people' => $people,
        ]);
    }
}

要调试此搜索查询,您可以在Laravel Scout的Elastic Engine上调用静态debug方法

use Hilsonxhero\ElasticVision\Infrastructure\Scout\ElasticEngine;

$debug = ElasticEngine::debug();

此方法返回的调试类可以以数组或JSON形式提供最后执行的查询。您应该能够将JSON复制粘贴为直接查询到Elasticsearch。

$lastQueryAsArray = ElasticEngine::debug()->array();
$lastQueryAsJson = ElasticEngine::debug()->json();

控制台命令

创建

使用Laravel Scout导入命令创建和更新索引。

php artisan scout:import <model>

例如,如果您的模型是"App\Models\Post",则命令如下

php artisan scout:import "App\Models\Post"

如果您想重新创建索引,请首先确保它已被删除,然后创建它。接着用scout导入命令来重新填充索引。

更新别名索引

如果您正在使用别名索引,则应使用此命令而不是scout:import

php artisan elastic:update <index?>

您可以选择指定索引或选择省略它,命令将更新所有索引。例如,如果您的模型是"App\Model\Post",索引是"posts",则

php artisan elastic:update posts

删除

php artisan scout:delete-index <model>

使用Laravel Scount删除索引命令来删除索引。

命令

请确保您已首先在config/elasticvision.php中配置索引并运行Scout命令。

📕 文档