mikemadisonweb / yii2-elasticsearch
为与Elasticsearch版本5.0及以上版本集成而设计的Yii2扩展。
Requires
- php: >=5.5
- elasticsearch/elasticsearch: ~5.2
- yiisoft/yii2: ^2.0
README
基于官方elasticsearch-php低级客户端的Yii2扩展,用于与Elasticsearch版本5.0及以上版本集成。
与elasticsearch-php相比,此扩展提供了更直观的操作方式,例如索引文档、搜索、倒排搜索(反向搜索)、使用简单的SQL-like语言构建复杂的过滤条件。此外,它高度可配置和可扩展,它不紧密绑定到ActiveRecord模型,但可以轻松实现。
此文档适用于该扩展的最新稳定版本。
安装
安装此扩展的首选方式是通过composer。
运行以下命令
php composer.phar require mikemadisonweb/yii2-elasticsearch
或
"mikemadisonweb/yii2-elasticsearch": "^1.2.0"
将以下内容添加到您的composer.json
文件的require部分。
配置
假设您有一个庞大的博客文章数据库,并且您想在标题和正文字段上使用全文搜索,也可以按关键词搜索,也许还需要按类别和标签进行过滤
<?php // config/main.php return [ // ... 'components' => [ // ... 'elasticsearch' => [ 'class' => \mikemadisonweb\elasticsearch\Configuration::class, 'clients' => [ 'default' => [ 'hosts' => [ 'server1.yourdomain.com', 'server2.yourdomain.com:9200', ], ], ], 'indexes' => [ [ 'index' => 'my-blog', 'client' => [ 'name' => 'default', ], 'body' => [ 'settings' => [ 'number_of_shards' => 5, 'number_of_replicas' => 1, ], 'mappings' => [ 'posts' => [ 'dynamic' => 'strict', // Validate upon indexing, optional 'properties' => [ 'title' => [ 'type' => 'text', ], 'body' => [ 'type' => 'text', ], 'keywords' => [ 'type' => 'keyword', ], 'category_id' => [ 'type' => 'integer', 'include_in_all' => false, ], 'tags' => [ 'type' => 'integer', 'include_in_all' => false, ], 'post_date' => [ 'type' => 'date', 'format' => 'epoch_second', // timestamp 'include_in_all' => false, ], ], ], ], ], ], ], ], // ... ], // ... ];
如果您需要,可以配置多个Elasticsearch客户端。这可能是您的应用程序使用的不同搜索集群。每个客户端可以定义为多个hosts
,此参数包含Elasticsearch服务器的主机或IP地址列表,可选地后跟端口号(默认为9200)。您可以在elasticsearch-php文档中找到更多选项。默认情况下,连接到这些服务器之一使用轮询策略。
在选择分片和副本的数量时,请考虑您的性能和持久性需求。这些参数的正确数量是这些特性的折衷。上述示例使用默认的分片和副本数量,因此如果您希望这样,可以在配置中省略这些参数。
指定索引名称和该索引所使用的客户端后,您需要指定所需的搜索字段。这些字段可以根据您认为合理和方便的方式分组到映射中(例如,'posts')。在全文搜索方面,添加到名为索引的任何单个数据称为文档。理解映射意义的一个简单方法是将其视为文档类型。默认情况下,Elasticsearch不会强制您为您的数据提供此类模式,因此如果您的文档包含比映射中列出的更多字段,则这些新字段也会无问题地存储。要更改此行为,可以将dynamic
设置为strict
,在插入时未指定的字段将引发错误。
当您插入新文档时,每个字段都根据其数据类型进行处理。例如,text
数据类型用于索引全文值(这些值将被分析),而keyword
可以按其确切值进行搜索,这对于过滤或聚合很有用。请注意,如果您想存储数组,无需显式定义array
数据类型,您可以在integer
数据类型中存储整数数组。
默认情况下,Elasticsearch在索引时会存储传入的文档,当您检索搜索结果时,您将在_source字段中找到原始内容。您可以通过设置映射配置中的参数来禁用该行为。
'_source' => [ 'enabled' => true ],
另一方面,您可以在特定的映射字段中设置'index' => false
以防止该字段被索引。
Elasticsearch还有一个特殊的_all字段,它将所有其他字段的值连接起来。当您未指定要搜索的字段列表时,会使用它,但它不会存储。
该扩展包含控制台命令,用于根据配置创建和删除索引。
<?php // config/main.php return [ // ... 'controllerMap' => [ 'elastic-index' => \mikemadisonweb\elasticsearch\commands\IndexController::class, ], // ... ];
用法
创建索引
配置完您的索引后,可以在Elasticsearch中创建它们。
php yii elastic-index/create 'my-blog'
如果您不提供索引名称,它将创建所有定义的索引。
索引文档
创建索引后,您可以像这样插入新记录
$indexer = \Yii::$app->elasticsearch->getIndexer('my-blog', 'posts'); $blogPost = [ 'title' => 'New in Elasticsearch 6.0', 'body' => 'Lots of stuff...', 'keywords' => 'Elasticsearch', 'category_id' => '3', 'tags' => [1, 43, 64], ]; $this->indexer->insert($blogPost);
插入方法有一个可选的第二个参数 - 一个唯一的id。您可以使用任何字符串作为id,每个索引中的文档都有一个该id,如果不传递该id,则Elasticsearch会自动生成。
Indexer类中的其他有用方法
IndexerResponse update(array $fields, string $id, array $upsert = [], $script = '', array $scriptParams = []) IndexerResponse delete(string $id, bool $ignoreMissing = false) array insertBatch(array $batch)
批量插入是一种加快索引过程的方法,并且是唯一返回数组而不是ElasticResponse对象的函数。批量中的文档数组可以是数字(不带id)或关联的,以id作为键。
搜索
该扩展提供了一个构建器,用于简化查询组合的过程。要对'title'字段执行全文搜索,您应使用match()
方法。
$finder = \Yii::$app->elasticsearch->getFinder('my-blog', 'posts'); $results = $finder ->match('How to use Elasticsearch', 'title') ->all();
还有过滤结果、排序或选择特定部分的方法。
$finder = \Yii::$app->elasticsearch->getFinder('my-blog', 'posts'); $results = $finder ->select(['title', 'body']) ->match('How to use Elasticsearch', 'title') ->where('category_id = 14') ->sort('post_date:desc') ->limit(100) ->offset(100) ->all(); foreach ($results as $result) { // ... }
请注意,如果您在where()
方法中使用分析字段(全文字段),可能会得到错误的结果,因为该方法仅适用于非分析数据类型(keyword、integer、boolean等)。match()
方法中有一个可选参数,您必须注意。
Finder match(string $query, array|string $fields = '_all', string $condition = 'and', string $operator = 'and', string $type = 'cross_fields')
通常需要将查询字符串与特定字段匹配,但如果您想在不同的字段中搜索不同的字符串(例如,在'title'中搜索'some text'和在'body'中搜索'another data'),则可以在condition
参数中选择逻辑运算符以满足您的需求。如果您在多个字段中进行搜索,可以定义operator
和type
参数以指定在这些字段上搜索的逻辑。
更复杂的过滤器可以使用类似SQL的方式指定。
$finder = \Yii::$app->elasticsearch->getFinder('my-blog', 'posts'); $results = $finder ->where("category_id = 11 OR (tags in [1, 53, 78] AND keywords = 'Elastica')") ->all();
如果您觉得Finder方法有限,可以传递原始JSON并直接与Elasticsearch API交互。
$json = '{ "query" : { "match" : { "body" : "Most important things in life" } } }'; $finder = \Yii::$app->elasticsearch->getFinder('my-blog', 'posts'); $results = $finder->sendJson($json);
分析
Elasticearch为分析文本提供了大量选项。分析器、规范化器和令牌过滤器超出了本文档的范围,您可以在官方文档中找到有关它们的大量信息。
以下是一个带有停用词过滤器和snowball词干还原器的俄语分析配置示例。
<?php // config/main.php return [ // index settings 'settings' => [ 'number_of_shards' => 1, 'number_of_replicas' => 0, 'analysis' => [ 'filter' => [ 'russian_stop' => [ 'type' => 'stop', 'stopwords' => '_russian_', ], 'russian_stemmer' => [ 'type' => 'stemmer', 'language' => 'russian', ], ], 'analyzer' => [ 'default' => [ 'tokenizer' => 'standard', 'filter' => [ 'lowercase', 'russian_stop', 'russian_stemmer', ], ], ], ], ], // ... ];
高亮显示
Elasticsearch提供了一种在源文本中突出显示搜索词的方式。如果你搜索“新的可能性”,那么某些文档中找到的“可能性”这个词会被像这样用标签包围<em>可能性</em>
。这些突出显示的结果将位于响应的“highlight”字段中,而不是“_source”字段中。有一个名为highlight()
的Finder方法,可以用于为单个查询开启此功能。你还可以将各种参数作为第二个参数传递
<?php $finder = \Yii::$app->elasticsearch->getFinder('my-blog', 'posts'); $results = $finder ->match('Find me!', 'body') ->highlight(true, [ 'fields' => [ 'body' => new \stdClass() ], 'pre_tags' => '<strong>', 'post_tags' => '</strong>', ]) ->all();
要全局开启此功能,你应在索引配置中启用它。默认排序顺序和限制选项也可以在那里进行配置
<?php // config/main.php return [ // index configuration 'index' => 'my-blog', 'defaults' => [ 'limit' => 100, 'sort' => 'post_date:desc', 'highlight' => [ 'enabled' => true, 'pre_tags' => '<span class=“highlight”>', 'post_tags' => '</span>', 'fields' => ['*' => ['number_of_fragments' => 0]] ], ], // ... ];
星号符号表示将在所有配置的分析字段中突出显示,但有一个问题。如果你想接收所有文本字段的突出显示结果,那么你不应该在match()
函数中将_all
作为搜索字段传递。