adeb6600/bouncy

将 Elasticsearch 结果映射到 Eloquent 模型

dev-master 2015-12-07 15:57 UTC

This package is not auto-updated.

Last update: 2024-09-14 18:20:32 UTC


README

要在 Laravel 5 中使用它,请查看 l5 分支。

Bouncy

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

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

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

目录

安装

  • 将软件包添加到您的 composer.json 文件中,并运行 composer update
{
    "require": {
        "fadion/bouncy": "dev-master"
    }
}
  • 将服务提供者添加到您的 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 需要一个索引来工作。通常这是一项繁琐的任务,但在这里变得非常简单。

索引所有记录

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();

限制

为了性能,应该使用size关键字在Elasticsearch参数列表上限制搜索结果。但是,为了方便限制,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风格的Facade来访问Elasticsearch的本地客户端。为了使这一步骤工作,你需要在app/config/app.php文件中的aliases数组中添加一个别名:'Elastic' => 'Fadion\Bouncy\Facades\Elastic'

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

// and any other method it provides