mammutgroup/bouncy

将 Elasticsearch 结果映射到 Eloquent 模型

1.4 2017-04-30 06:47 UTC

This package is not auto-updated.

Last update: 2024-09-14 18:53:14 UTC


README

要在 Laravel 5 中使用它,请参阅 l5 分支。

Bouncy

Elasticsearch 是一款优秀的搜索引擎,但将其结果转换为易于使用的数据集需要一些工作。Bouncy 正好可以做到这一点:它将 Elasticsearch 结果映射到 Eloquent 模型,因此您可以继续使用相同的逻辑,同时增加一些特殊功能。此外,它还处理索引,无论是手动还是自动在模型创建、更新或删除时进行。

此包是为一个个人项目创建的,它仍在开发中。然而,我不期望它的 API 发生变化。

我受到了启发,大部分实现基于 Elasticquent。基本上,它是对那个包的重写分支。向开发者致敬。

目录

安装

  • 将包添加到您的 composer.json 文件中,并运行 composer update
{
    "require": {
        "fadion/bouncy": "~1.0"
    }
}
  • 将服务提供者添加到您的 app/config/app.php 文件中,在 providers 数组内:'Fadion\Bouncy\BouncyServiceProvider'

  • 通过在终端中运行以下命令发布配置文件:php artisan config:publish fadion/bouncy

  • 编辑配置文件(位于 app/config/packages/bouncy/),并设置 Elasticsearch 索引名称、服务器配置等。

设置

让您的模型知道它们应该使用 Bouncy,只需添加一个特质即可!我将使用一个虚构的 Product 模型进行示例。

use Fadion\Bouncy\BouncyTrait;

class Product extends Eloquent {
    
    use BouncyTrait;
    
    // ...other Eloquent attributes
    // or methods.
}

索引和类型名称

索引可以在配置文件中设置,而类型名称会自动从模型的表名中获取。这通常是组织文档的好方法,因为您只需配置一次,就可以忽略它。

当您需要专门设置索引或类型名称时,只需将以下属性添加到您的模型中

class Product extends Eloquent {

    protected $indexName = 'awesome_index';
    protected $typeName = 'cool_type';

}

索引

在进行任何搜索查询之前,Elasticsearch 需要一个索引来工作。通常这是一个繁琐的任务,但 Bouncy 使其变得非常简单。

索引所有记录

Product::all()->index();

索引一组模型

Product::where('sold', true)->get()->index();

索引单个模型

$product = Product::find(10);
$product->index();

集合索引将批量添加,Elasticsearch 处理得相当快。但是,请注意,索引大型集合是一个耗尽资源的过程。击中 SQL 数据库并迭代每一行需要时间和资源,因此请尽量保持集合相对较小。您将不得不根据您的服务器资源和配置实验一次可以索引的数据量。

更新索引

更新是安全地重新索引现有文档的一种方式,在版本冲突方面。当模型存在且其任何属性已更改时,它的索引将被更新。否则,它将作为调用 index() 方法一样添加到索引中。

更新模型的索引

$product = Product::find(10);
$product->price = 100;
$product->updateIndex();

使用自定义属性更新模型的索引。这种用法很少,因为最好是保持模型和索引同步,但在需要时它就在那里。

$product = Product::find(10);
$product->updateIndex([
    'price' => 120,
    'sold' => false
]);

删除索引

当您完成一个模型的索引时,显然您可以删除它。

移除集合的索引

Product::where('quantity', '<', 25)->get()->removeIndex();

移除单个模型的索引

$product = Product::find(10);
$product->removeIndex();

该方法故意命名为'remove'而不是'delete',以免与Eloquent的delete()方法混淆。

重新索引

一个便捷方法,实际上会移除索引后再添加。这在您希望文档获得一个全新的索引、重置版本信息时非常有用。

重新索引集合

Product::where('condition', 'new')->get()->reindex();

重新索引单个模型

$product = Product::find(10);
$product->reindex();

并发控制

Elasticsearch假设在索引过程中不会出现文档冲突,因此它不提供自动并发控制。然而,它确实提供了文档的版本信息,可用于确保旧文档不会覆盖新文档。这是手册中描述的推荐技术。

Bouncy提供了一个通过检查版本进行索引的单个方法。它只会在指定的版本与文档中的版本匹配时更新索引,否则返回false。显然,它是并发安全的。

$product = Product::find(10);
$product->indexWithVersion(3);

自动索引

Bouncy知道何时创建、保存或删除模型,并将这些更改反映到索引中。除了现有数据库的初始索引创建外,通常您不需要使用上述方法来操作索引。任何新的模型索引都将自动添加,在保存时更新,在模型被删除时移除。

您可以通过将auto_index配置选项设置为false来完全禁用自动索引。这样做后,您需要自己将数据库同步到索引。

Bouncy无法更新或删除索引的唯一情况是在执行大量更新或删除时。这些查询直接在查询构建器上运行,无法覆盖它们。我正在寻找一种更好的方法来做这件事,但到目前为止,以下查询不会反映索引上的更改

Product::where('price', 100)->update(['price' => 110]);
// or
Product::where('price', 100)->delete();

您仍然可以手动调用索引方法并解决限制。这会增加一个数据库查询,但至少可以保持您的数据同步。

Product::where('price', 100)->get()->updateIndex(['price' => 110]);
Product::where('price', 100)->update(['price' => 110]);
// or
Product::where('price', 100)->get()->removeIndex();
Product::where('price', 100)->delete();

映射

映射可以像您之前看到的任何内容一样轻松创建。它们定义为模型的类属性,并使用一些简单的方法处理。

向模型添加映射属性

use Fadion\Bouncy\BouncyTrait;

class Product extends Eloquent {
    
    use BouncyTrait;
    
    protected $mappingProperties = [
        'title' => [
            'type' => 'string',
            'store' => true
        ],
        'description' => [
            'type' => 'string',
            'index' => 'analyzed'
        ]
    ]
    
}

放置这些映射

Product::putMapping();

获取映射

Product::getMapping();

删除映射

Product::deleteMapping();

重建(删除并再次放置)映射

Product::rebuildMapping();

检查映射是否存在

if (Product::hasMapping()) {
    // do something
}

搜索

现在让我们谈谈真正的重点!搜索是Elasticsearch的强项,也是您为什么要使用它的原因。Bouncy不会干扰您,允许您以与Elasticsearch客户端完全相同的方式构建任何搜索查询。这提供了极大的灵活性,同时将Eloquent模型集合的结果提供给您。

一个匹配查询的示例

$params = [
    'query' => [
        'match' => [
            'title' => 'github'
        ]
    ],
    'size' => 20
];

$products = Product::search($params);

foreach ($products as $product) {
    echo $product->title;
}

$params数组完全符合Elasticsearch的期望,以构建JSON请求。这里没有新内容!您可以轻松构建任何搜索查询,无论是匹配、多匹配、more_like_this等。

分页

分页结果在应用程序中非常重要,而原始Elasticsearch结果通常很难进行分页。这也是使用Bouncy的一个好理由!它以与Eloquent相同的方式分页结果,因此您不需要学习任何新内容。

每页显示15个模型(默认值)

$products = Product::search($params)->paginate();

分页到任意数量

$products = Product::search($params)->paginate(30);

在您的视图中,您可以像以前一样显示分页链接

$products->links();

限制

为了性能,应该在Elasticsearch参数列表中使用size关键字对搜索结果进行限制。然而,为了便于限制,Bouncy提供了这项功能。

限制到50个结果

$products = Product::search($params)->limit(50);

结果信息

Elasticsearch提供了有关查询的一些信息,例如总命中数或所需时间。Bouncy的结果集合有方法可以轻松访问这些信息。

$products = Product::search($params);

$products->total(); // Total number of hits
$products->maxScore(); // Maximum score of the results
$products->took(); // Time in ms it took to run the query
$products->timedOut(); // Wheather the query timed out, or not.

$products->shards(); // Array of shards information
$products->shards($key); // Information on specific shard

文档信息

Elasticsearch文档有一些信息,如得分和版本。您可以使用以下方法访问这些数据

$products = Product::search($params);

foreach ($products as $product) {
    $product->isDocument(); // Checks if it's an Elasticsearch document
    $product->documentScore(); // Score set in search results
    $product->documentVersion(); // Document version if present
}

高亮显示

高亮是一个很好的视觉功能,可以增强您的搜索结果。Bouncy让访问高亮字段变得非常简单。

$params = [
    'query' => [
        'match' => [
            'title' => 'github'
        ]
    ],
    'highlight' => [
        'fields' => [
            'title' => new \stdClass
        ]
    ]
];

$products = Product::search($params);

foreach ($products as $product) {
    echo $product->highlight('title');
}

highlight() 方法将访问具有提供名称的任何高亮字段,如果找不到则静默失败(实际上,返回 false)。

搜索简写

灵活性和所有这些都是很棒的,但在某些情况下,您可能只需要运行一个简单的匹配查询并完成任务,而不必编写完整的参数数组。Bouncy提供了一些简写方法来处理最常见的搜索查询。它们与 search() 方法的工作方式和处理结果的方式相同,因此所有上述内容也适用于它们。

匹配查询

$products = Product::match($title, $query)

多匹配查询

$products = Product::multiMatch(Array $fields, $query)

模糊查询

$products = Product::fuzzy($field, $value, $fuzziness = 'AUTO')

地理形状查询

$products = Product::geoshape($field, Array $coordinates, $type = 'envelope')

ids 查询

$products = Product::ids(Array $values)

更多类似查询

$products = Product::moreLikeThis(Array $fields, Array $ids, $minTermFreq = 1, $percentTermsToMatch = 0.5, $minWordLength = 3)

自定义文档字段

Bouncy在索引时会使用您模型的属性,这在大多数情况下应该没问题。然而,如果您想控制Elasticsearch文档的结构,您可以通过在模型中添加一个 $documentFields 方法来自定义字段。

use Fadion\Bouncy\BouncyTrait;

class Product extends Eloquent {
    
    use BouncyTrait;
    
    public function documentFields()
    {
        return [
            'id' => $this->id,
            'price' => $this->price,
            'rating' => 'perfect'
        ];
    };

}

自定义集合

如果您正在使用Eloquent的自定义集合,您仍然可以使用Bouncy的方法。您只需要为您的集合类添加一个特质。

use Illuminate\Database\Eloquent\Collection;
use Fadion\Bouncy\BouncyCollectionTrait;

class MyAwesomeCollection extends Collection {

    use BouncyCollectionTrait;

}

Elasticsearch 客户端外观

最后,当您需要时,您可以使用Facade以Laravel的方式访问Elasticsearch的本地客户端。为了使此步骤正常工作,您需要在app/config/app.php中的别名字符串数组中添加一个别名:'Elastic' => 'Fadion\Bouncy\Facades\Elastic'

Elastic::index();
Elastic::get();
Elastic::search();
Elastic::indices()->create();

// and any other method it provides