fadion/bouncy

将 Elasticsearch 结果映射到 Eloquent 模型

1.0 2015-08-20 21:45 UTC

This package is auto-updated.

Last update: 2024-09-21 20:07:49 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 客户端外观

最后,当你需要它时,你可以使用门面以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