huscakmak / elasticsearch
Laravel 缺失的 Elasticsearch ORM!
Requires
- php: >=7.3|^8.0
- ext-json: *
- elasticsearch/elasticsearch: ^7.11
- illuminate/pagination: *
- illuminate/support: *
- monolog/monolog: *
- symfony/var-dumper: *
Requires (Dev)
- illuminate/contracts: ^8.5
- illuminate/database: ^8.5
- laravel/scout: ^9.1
- orchestra/testbench: ^6.9
- phpunit/phpunit: ^9.3
- vimeo/psalm: ^4.6
Replaces
README
Laravel Elasticsearch 集成
这是基于 @basemkhirat 的优秀库的分支,遗憾的是,他似乎已经放弃了这个库。
由于我们相当依赖这个库,我们将努力保持其更新并兼容新的 Laravel 和 Elasticsearch 版本。
本分支的更改
- 支持 Elasticsearch 7.10 及更高版本
- 支持 PHP 7.3 及更高版本(包括 PHP 8!)
- 扩展了对 Laravel 库的支持,允许您使用几乎所有的 Laravel 版本
- 在所有支持的位置添加了类型提示,对所有参数都充满信心
- 用于高级自动完成的 Docblock 注释,详尽的内联文档
- 将连接管理干净地分离到
ConnectionManager类,同时保留向后兼容性 - 支持 大多数 Eloquent 模型行为(见下文)
- 删除了对 Laravel 内部的依赖
如果您有兴趣贡献,请提交一个 PR 或打开一个问题!
功能
- 具有优雅语法的流畅 Elasticsearch 查询构建器
- 受 Laravel Eloquent 启发的 Elasticsearch 模型
- 使用简单的 artisan 命令进行索引管理
- 有限支持 Lumen 框架
- 可以作为 Laravel Scout 驱动程序使用
- 并行使用多个 Elasticsearch 连接
- 基于 Laravel Pagination 的内置分页
- 使用基于 laravel cache 的缓存层进行查询缓存
目录
- 要求
- 安装
- 配置(Laravel & Lumen)
- Artisan 命令(Laravel & Lumen)
- 作为 Laravel Scout 驱动程序使用
- Elasticsearch 模型
- 作为查询构建器使用
- 版本
- 作者
- 错误、建议和贡献
- 许可证
要求
- PHP >=
7.3
见 Travis CI 构建。 laravel/laravel>= 5.* 或laravel/lumen>= 5.* 或任何使用 composer 的其他应用
安装
本节描述了所有支持的应用类型安装过程。
使用 composer 安装包
无论您使用的是 Laravel、Lumen 还是其他框架,请首先使用 composer 安装此包
composer require Huslab/elasticsearch
Laravel 安装
如果您已禁用包自动发现,请将服务提供者和外观添加到您的 config/app.php
'providers' => [ // ... Huslab\Elasticsearch\ElasticsearchServiceProvider::class, // ... ], // ... 'aliases' => [ // ... 'ES' => Huslab\Elasticsearch\Facades\ES::class, // ... ],
最后,将服务提供者发布到您的配置目录
php artisan vendor:publish --provider="Huslab\Elasticsearch\ElasticsearchServiceProvider"
Lumen 安装
从 composer 安装包后,请在 bootstrap/app.php 中添加包服务提供者
$app->register(Huslab\Elasticsearch\ElasticsearchServiceProvider::class);
将包配置目录 vendor/Huslab/elasticsearch/src/config/ 复制到您的项目根目录,与您的 app/ 目录并列
cp -r ./vendor/Huslab/elasticsearch/src/config ./config
如果您还没有,请取消注释 bootstrap/app.php 中的此行,以使 Lumen 与外观协同工作
$app->withFacades();
如果您不想在 Lumen 中启用外观,可以使用 app("es") 访问查询构建器
app("es")->index("my_index")->type("my_type")->get(); # This is similar to: ES::index("my_index")->type("my_type")->get();
通用应用程序安装
您可以使用任何基于 composer 的应用安装此包。虽然我们无法提供通用说明,但以下示例应能给您一个大致了解如何使用
require "vendor/autoload.php"; use Huslab\Elasticsearch\ConnectionManager; use Huslab\Elasticsearch\Factories\ClientFactory; $connectionManager = new ConnectionManager([ 'servers' => [ [ "host" => '127.0.0.1', "port" => 9200, 'user' => '', 'pass' => '', 'scheme' => 'http', ], ], // Custom handlers // 'handler' => new MyCustomHandler(), 'index' => 'my_index', ], new ClientFactory()); $connection = $connectionManager->connection(); // Access the query builder using created connection $documents = $connection->search("hello")->get();
配置(Laravel & Lumen)
发布服务提供者后,将在 config/es.php 中创建一个配置文件。在此处,您可以添加一个或多个 Elasticsearch 连接,每个连接可以有多个服务器。以下是一个示例
# Here you can define the default connection name. 'default' => env('ELASTIC_CONNECTION', 'default'), # Here you can define your connections. 'connections' => [ 'default' => [ 'servers' => [ [ "host" => env("ELASTIC_HOST", "127.0.0.1"), "port" => env("ELASTIC_PORT", 9200), 'user' => env('ELASTIC_USER', ''), 'pass' => env('ELASTIC_PASS', ''), 'scheme' => env('ELASTIC_SCHEME', 'http'), ] ], // Custom handlers // 'handler' => new MyCustomHandler(), 'index' => env('ELASTIC_INDEX', 'my_index') ] ], # Here you can define your indices. 'indices' => [ 'my_index_1' => [ "aliases" => [ "my_index" ], 'settings' => [ "number_of_shards" => 1, "number_of_replicas" => 0, ], 'mappings' => [ 'posts' => [ 'properties' => [ 'title' => [ 'type' => 'string' ] ] ] ] ] ]
如果您想使用 Laravel Scout 与 Elasticsearch,您可以在 config/scout.php 中找到 scout 特定的设置。
Artisan 命令(Laravel & Lumen)
本包包含的 artisan 命令可以帮助您创建或更新设置、映射和别名。请注意,所有命令默认使用默认连接。您可以通过传递 --connection <your_connection_name> 选项来更改此设置。
以下命令可用
es:indices:list:列出服务器上的所有索引
$ php artisan es:indices:list
+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+
| configured (es.php) | health | status | index | uuid | pri | rep | docs.count | docs.deleted | store.size | pri.store.size |
+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+
| yes | green | open | my_index | 5URW60KJQNionAJgL6Q2TQ | 1 | 0 | 0 | 0 | 260b | 260b |
+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+
es:indices:create:创建在 config/es.php 中定义的索引
请注意,创建操作会跳过已存在的索引。
# Create all indices in config file. php artisan es:indices:create # Create only 'my_index' index in config file php artisan es:indices:create my_index
es:indices:update:更新在 config/es.php 中定义的索引
请注意,更新操作会更新索引设置、别名和映射,但不会删除索引数据。
# Update all indices in config file. php artisan es:indices:update # Update only 'my_index' index in config file php artisan es:indices:update my_index
es:indices:drop:删除索引
在使用此命令时请小心,因为您将丢失索引数据!
使用带 --force 选项的 drop 命令将跳过所有确认消息。
# Drop all indices in config file. php artisan es:indices:drop # Drop specific index on sever. Not matter for index to be exist in config file or not. php artisan es:indices:drop my_index
数据重新索引(零停机时间)
首先,为什么需要重新索引?
更改索引映射不会反映出来,除非重新索引数据,否则搜索结果将无法正确工作。
为了避免停机时间,您的应用程序应使用索引 alias 而不是索引 name 工作。
索引 alias 是一个常量名称,应用程序应使用它来避免更改索引名称。
假设我们想更改 my_index 的映射,以下是操作步骤
-
将
alias添加为示例my_index_alias到my_index配置,并确保您的应用程序正在使用它。"aliases" => [ "my_index_alias" ]
-
使用命令更新索引
php artisan es:indices:update my_index
-
在配置文件中创建一个新索引,例如
my_new_index,并包含您的新映射。$ php artisan es:indices:create my_new_index
-
使用命令将
my_index中的数据重新索引到my_new_indexphp artisan es:indices:reindex my_index my_new_index # Control bulk size. Adjust it with your server. php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 # Control query scroll value. php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --scroll=2m # Skip reindexing errors such as mapper parsing exceptions. php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --skip-errors # Hide all reindexing errors and show the progres bar only. php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --skip-errors --hide-errors
-
在配置文件中将
my_index_alias别名从my_index移除,并添加到my_new_index,然后使用命令更新php artisan es:indices:update
作为 Laravel Scout 驱动程序使用
首先,请遵循 Laravel Scout 安装。
您只需要更新 config/scout.php 中的以下行
# change the default driver to 'es' 'driver' => env('SCOUT_DRIVER', 'es'), # link `es` driver with default elasticsearch connection in config/es.php 'es' => [ 'connection' => env('ELASTIC_CONNECTION', 'default'), ],
请参阅 Laravel Scout 文档,以了解更多信息!
Elasticsearch 模型
每个索引类型都有一个相应的 "Model",用于与该类型交互。模型允许您在类型或索引中查询数据,以及向类型中插入新文档。Elasticsearch 模型尽可能模仿 Eloquent 模型:您可以使用模型事件、路由绑定、高级属性方法等。如果缺少任何 Eloquent 功能,请提交问题,我们将很高兴为您添加它!
支持的功能
- 属性
- 事件
- 路由绑定
- 全局和本地查询范围
- 复制模型
一个最小化模型可能看起来像这样
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { // ... }
索引名称
此模型并未绑定到任何索引,它将简单地使用为给定的Elasticsearch连接配置的索引。若要针对特定索引进行操作,您可以在模型上定义一个index属性
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { protected $index = 'posts'; }
连接名称
默认情况下,所有Elasticsearch模型都将使用为您的应用程序配置的默认连接。如果您想指定一个不同的连接,该连接在交互特定模型时应使用,您应在模型上定义一个$connection属性
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { protected $connection = 'blag'; }
映射类型
如果您仍在使用映射类型,您可以在模型上添加一个type属性来指示查询应使用的映射_type。
映射类型已弃用
请注意,Elastic已弃用映射类型,并在下一个主要版本中删除它们。您不应依赖它们以继续工作。
namespace App; use Huslab\Elasticsearch\Model; class Post extends Model { protected $type = 'posts'; }
默认属性值
默认情况下,新实例化的模型实例不会包含任何属性值。如果您想为模型的一些属性定义默认值,您可以在模型上定义一个attributes属性
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { protected $attributes = [ 'published' => false, ]; }
检索模型
一旦您创建了模型及其相关索引类型,您就可以开始从索引中检索数据。您可以将Elasticsearch模型视为一个强大的查询构建器,允许您流畅地查询与模型关联的索引。模型上的all方法将检索模型关联的Elasticsearch索引中的所有文档
use App\Models\Post; foreach (Post::all() as $post) { echo $post->title; }
添加额外的约束
all方法将返回模型索引中的所有结果。然而,由于每个Elasticsearch模型都充当查询构建器,您可以在查询中添加额外的约束,然后调用get()方法来检索结果
use App\Models\Post; $posts = Post::where('status', 1) ->orderBy('created_at', 'desc') ->take(10) ->get();
集合
正如我们所见,Elasticsearch方法如all和get从索引中检索多个文档。然而,这些方法不返回一个普通的PHP数组。相反,返回的是Huslab\Elasticsearch\Collection的一个实例。
Elasticsearch的Collection类扩展了Laravel的基础Illuminate\Support\Collection类,这提供了一系列用于与数据集合交互的有用方法。例如,可以使用reject方法根据调用的闭包的结果从集合中删除模型。
use App\Models\Post; $posts = Post::where('sponsored', true)->get(); $posts = $posts->reject($post => $post->in_review);
除了Laravel基础集合类提供的方法外,Elasticsearch集合类还提供了一些旨在专门与Elasticsearch模型集合交互的额外方法。
结果元数据
Elasticsearch除了查询的命中外,还提供了一些额外的字段,如总结果数或查询执行时间。Elasticsearch集合提供了这些属性的getter方法。
use App\Models\Post; $posts = Post::all(); $total = $posts->getTotal(); $maxScore = $posts->getMaxScore(); $duration = $posts->getDuration(); $isTimedOut = $posts->isTimedOut(); $scrollId = $posts->getScrollId(); $shards = $posts->getShards();
迭代
由于Laravel的所有集合都实现了PHP的iterable接口,因此您可以像数组一样遍历集合。
foreach ($title as $title) { echo $post->title; }
分块结果
Elasticsearch索引可以变得非常大。如果您的应用程序尝试通过all或get方法加载数十万个Elasticsearch文档而没有上限,它可能会耗尽内存。因此,默认的文档检索数量设置为10。要更改此值,请使用take方法。
use App\Models\Post; $posts = Post::take(500)->get();
检索单个模型
除了检索匹配给定查询的所有文档外,您还可以使用find、first或firstWhere方法检索单个文档。这些方法不返回模型集合,而是返回单个模型实例。
use App\Models\Post; // Retrieve a model by its ID... $posts = Post::find('AVp_tCaAoV7YQD3Esfmp'); // Retrieve the first model matching the query constraints... $post = Post::where('published', 1)->first(); // Alternative to retrieving the first model matching the query constraints... $post = Post::firstWhere('published', 1);```
有时你可能希望检索查询的第一个结果或在没有找到结果时执行某些其他操作。firstOr 方法将返回与查询匹配的第一个结果,如果没有找到结果,将执行给定的闭包。闭包返回的值将被视为 firstOr 方法的返回结果
use App\Models\Post; $model = Post::where('tags', '>', 3)->firstOr(function () { // ... });
未找到异常
有时你可能希望在找不到模型时抛出异常。这在路由或控制器中特别有用。findOrFail 和 firstOrFail 方法将检索查询的第一个结果;然而,如果没有找到结果,将抛出 Huslab\Elasticsearch\Exceptions\DocumentNotFoundException 异常
$post = Post::findOrFail('AVp_tCaAoV7YQD3Esfmp'); $post = Post::where('published', true)->firstOrFail();
如果未捕获 DocumentNotFoundException,系统将自动向客户端发送一个 404 HTTP 响应
use App\Models\Post; Route::get('/api/posts/{id}', function ($id) { return Post::findOrFail($id); });
插入和更新模型
插入
要将新文档插入索引,你应该创建一个新的模型实例并设置模型上的属性。然后,在模型实例上调用 save 方法
namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\Request; use Illuminate\Http\Response; use App\Http\Controllers\Controller; class PostController extends Controller { /** * Create a new post instance. * * @param Request $request * @return Response */ public function store(Request $request): Response { // Validate the request... $post = new Post; $post->title = $request->title; $post->save(); } }
在这个示例中,我们将传入的 HTTP 请求中的 name 字段分配给 App\Models\Post 模型实例的 name 属性。当我们调用 save 方法时,文档将被插入到索引中。
或者,你可以使用 create 方法通过单个 PHP 语句“保存”新模型。通过 create 方法插入的模型实例将返回给你
use App\Models\Post; $post = Post::create([ 'title' => 'Searching efficiently', ]);
但是,在使用 create 方法之前,你需要在你的模型类上指定一个 fillable 或 guarded 属性。这些属性是必需的,因为所有 Elasticsearch 模型默认情况下都受到批量赋值漏洞的保护。有关批量赋值的更多信息,请参阅批量赋值文档。
更新
save 方法也可以用来更新索引中已经存在的模型。要更新一个模型,你应该检索它并设置你想要更新的任何属性。然后,你应该调用模型的 save 方法。
save() 方法也可以用来更新索引中已经存在的模型。要更新一个模型,你应该检索它,设置你想要更新的任何属性,然后调用保存方法。
use App\Models\Post; $post = Post::find('AVp_tCaAoV7YQD3Esfmp'); $post->title = 'Modified Post Title'; $post->save();
检查属性更改
Elasticsearch 提供了 isDirty、isClean 和 wasChanged 方法来检查模型内部状态,并确定其属性与最初检索时的变化。
isDirty 方法确定自模型检索以来是否有任何属性被更改。你可以向 isDirty 方法传递一个特定的属性名称,以确定特定的属性是否已被更改。该方法还接受一个可选的属性参数
use App\Models\Author; $author = Author::create([ 'first_name' => 'Moritz', 'last_name' => 'Friedrich', 'title' => 'Developer', ]); $author->title = 'Painter'; $author->isDirty(); // true $author->isDirty('title'); // true $author->isDirty('first_name'); // false $author->isClean(); // false $author->isClean('title'); // false $author->isClean('first_name'); // true $author->save(); $author->isDirty(); // false $author->isClean(); // true
wasChanged 方法确定在当前请求周期内模型最后一次保存时是否有任何属性被更改。如果需要,你可以传递一个属性名称以查看特定的属性是否被更改
use App\Models\Author; $author = Author::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer', ]); $author->title = 'Painter'; $author->save(); $author->wasChanged(); // true $author->wasChanged('title'); // true $author->wasChanged('first_name'); // false
getOriginal 方法返回一个数组,包含模型的原生属性,无论模型自检索以来是否发生变化。如果需要,你可以传递一个特定的属性名称以获取特定属性的原始值
use App\Models\Author; $author = Author::find(1); $author->name; // John $author->email; // john@example.com $author->name = "Jack"; $author->name; // Jack $author->getOriginal('name'); // John $author->getOriginal(); // Array of original attributes...
批量赋值
你可以使用 create 方法通过单个 PHP 语句“保存”新模型。通过该方法插入的模型实例将返回给你
use App\Models\Post; $post = Post::create([ 'title' => 'Searching effectively', ]);
但是,在使用 create 方法之前,你需要在你的模型类上指定一个 fillable 或 guarded 属性。这些属性是必需的,因为所有 Elasticsearch 模型默认情况下都受到批量赋值漏洞的保护。
批量赋值漏洞发生在用户传递一个意外的 HTTP 请求字段,并且该字段更改了你不期望在索引中更改的字段时。
因此,要开始,您应该定义要使可批量赋值的模型属性。您可以使用模型上的fillable属性来完成此操作。例如,让我们使我们的Post模型的title属性可批量赋值
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['title']; }
一旦指定了哪些属性可批量赋值,您就可以使用create方法在索引中插入新文档。该方法返回新创建的模型实例
$post = Post::create(['title' => 'Searching effectively']);
如果您已经有了模型实例,您可以使用fill方法使用属性数组填充它
$post->fill(['title' => 'Searching more effectively']);
允许批量赋值
如果您想使所有属性可批量赋值,您可以将模型的guarded属性定义为空数组。如果您选择解除模型的保护,您应特别注意始终手动创建传递给Elasticsearch的fill、create和update方法的数组
/** * The attributes that aren't mass assignable. * * @var array */ protected $guarded = [];
更新或插入
目前没有用于更新文档(根据模型是否存在插入或更新)的便捷包装器。如果您对此类功能感兴趣,请提交问题。
删除模型
要删除模型,请在模型实例上调用delete方法
use App\Models\Post; $post = Post::find('AVp_tCaAoV7YQD3Esfmp'); $post->delete();
通过其 ID 删除现有的模型
在上面的示例中,我们在调用delete方法之前从索引中检索模型。但是,如果您知道模型的ID,您可以通过调用destroy方法删除模型而无需显式检索它。除了接受单个ID之外,destroy方法还将接受多个ID、ID数组或ID集合
use App\Models\Post; Post::destroy(1); Post::destroy(1, 2, 3); Post::destroy([1, 2, 3]); Post::destroy(collect([1, 2, 3]));
重要
destroy方法逐个加载每个模型并调用delete方法,以便为每个模型正确派发deleting和deleted事件。
查询作用域
查询范围实现方式与Eloquent中完全相同。
全局作用域
全局范围允许您向特定模型的查询添加约束。编写自己的全局范围可以提供一种方便、简单的方法,以确保针对给定模型的每个查询都接收某些约束。
编写全局范围
编写全局范围很简单。首先,定义一个实现Huslab\Elasticsearch\Interfaces\ScopeInterface接口的类。Laravel没有规定您应该将范围类放在哪个位置,因此您可以将此类放在您希望放置的任何目录中。
ScopeInterface要求您实现一个方法:apply。根据需要,apply方法可以添加约束或其他类型的子句到查询中。
namespace App\Scopes; use Huslab\Elasticsearch\Query; use Huslab\Elasticsearch\Model; use Huslab\Elasticsearch\Interfaces\ScopeInterface; class AncientScope implements ScopeInterface { /** * Apply the scope to a given Elasticsearch query builder. * * @param \Huslab\Elasticsearch\Query $query * @param \Huslab\Elasticsearch\Model $model * @return void */ public function apply(Query $query, Model $model) { $query->where('created_at', '<', now()->subYears(2000)); } }
应用全局范围
要将全局范围分配给模型,您应覆盖模型的booted方法并调用模型的addGlobalScope方法。该方法只接受您的范围实例作为其唯一参数
namespace App\Models; use App\Scopes\AncientScope; use Huslab\Elasticsearch\Model; class Post extends Model { /** * The "booted" method of the model. * * @return void */ protected static function booted() { static::addGlobalScope(new AncientScope); } }
匿名全局范围
Elasticsearch还允许您使用闭包定义全局范围,这对于不需要单独类的简单范围特别有用。当使用闭包定义全局范围时,您应提供自己选择的名称作为addGlobalScope方法的第一个参数。
namespace App\Models; use Huslab\Elasticsearch\Query; use Huslab\Elasticsearch\Model; class Post extends Model { /** * The "booted" method of the model. * * @return void */ protected static function booted(): void { static::addGlobalScope('ancient', function (Query $query) { $query->where('created_at', '<', now()->subYears(2000)); }); } }
移除全局范围
如果您想为特定查询移除全局范围,您可以使用withoutGlobalScope方法。该方法只接受全局范围类的名称作为其唯一参数
Post::withoutGlobalScope(AncientScope::class)->get();
或者,如果您使用闭包定义了全局范围,您应传递分配给全局范围的字符串名称
Post::withoutGlobalScope('ancient')->get();
如果您想移除多个或甚至所有查询的全局范围,您可以使用withoutGlobalScopes方法
// Remove all of the global scopes... Post::withoutGlobalScopes()->get();
// Remove some of the global scopes... Post::withoutGlobalScopes([ FirstScope::class, SecondScope::class ])->get();
局部作用域
局部作用域允许您定义一组通用的查询约束,您可以在整个应用程序中轻松重复使用。例如,您可能需要频繁检索所有被认为是“受欢迎”的帖子。
编写局部作用域
要定义作用域,请在Elasticsearch模型方法前加上scope前缀。作用域应始终返回一个查询构建器实例
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { /** * Scope a query to only include popular posts. * * @param \Huslab\Elasticsearch\Query $query * @return \Huslab\Elasticsearch\Query */ public function scopePopular(Query $query): Query { return $query->where('votes', '>', 100); } /** * Scope a query to only include published posts. * * @param \Huslab\Elasticsearch\Query $query * @return \Huslab\Elasticsearch\Query */ public function scopePublished(Query $query): Query { return $query->where('published', 1); } }
使用局部作用域
一旦定义了作用域,您可以在查询模型时调用作用域方法。但是,在调用方法时不应包含作用域前缀。您甚至可以链式调用多个作用域
use App\Models\Post; $posts = Post::popular()->published()->orderBy('created_at')->get();
动态作用域
有时您可能希望定义一个接受参数的作用域。要开始,只需将您的额外参数添加到作用域方法的签名中。作用域参数应定义在 $query 参数之后
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { /** * Scope a query to only include posts of a given type. * * @param \Huslab\Elasticsearch\Query $query * @param mixed $type * @return \Huslab\Elasticsearch\Query */ public function scopeOfType(Query $query, $type): Query { return $query->where('type', $type); } }
一旦将预期参数添加到作用域方法的签名中,您可以在调用作用域时传递这些参数
$posts = Post::ofType('news')->get();
比较模型
有时您可能需要确定两个模型是否“相同”。is方法可用于快速验证两个模型具有相同的ID、索引、类型和连接
if ($post->is($anotherPost)) { // }
事件
Elasticsearch模型会触发多个事件,允许您在模型的生命周期中的以下时刻挂钩: retrieved、creating、created、updating、updated、saved、saved、deleting、deleted、restoring、restored和replicating。
retrieved事件将在从索引检索现有模型时触发。当新模型首次保存时,将触发creating和created事件。当现有模型被修改并调用save方法时,将触发updating / updated事件。当模型创建或更新时(即使模型的属性没有发生变化),将触发saving / saved事件。
要开始监听模型事件,请在您的Elasticsearch模型上定义一个dispatchesEvents属性。此属性将Elasticsearch模型的生命周期中的各个点映射到您的自己的事件类。每个模型事件类都应该期望通过其构造函数接收受影响模型的一个实例
namespace App\Models; use Huslab\Elasticsearch\Model; use App\Events\UserDeleted; use App\Events\UserSaved; class Post extends Model { /** * The event map for the model. * * @var array */ protected $dispatchesEvents = [ 'saved' => PostSaved::class, 'deleted' => PostDeleted::class, ]; }
定义和映射事件后,您可以使用事件监听器来处理事件。
使用闭包
除了使用自定义事件类之外,您还可以注册在各种模型事件触发时执行的闭包。通常,您应该在模型的booted方法中注册这些闭包
namespace App\Models; use Huslab\Elasticsearch\Model; class Post extends Model { /** * The "booted" method of the model. * * @return void */ protected static function booted(): void { static::created(function ($post) { // }); } }
如有需要,在注册模型事件时,您可以使用可排队匿名事件监听器。这将指示Laravel在后台使用您的应用程序的队列执行模型事件监听器
use function Illuminate\Events\queueable; static::created(queueable(function ($post): void { // }));
访问器和修改器
定义访问器
要定义访问器,请在您的模型上创建一个名为getFooAttribute的方法,其中Foo是您希望访问的字段的“studly”格式名称。在这个例子中,我们将为title属性定义一个访问器。访问器将自动由模型在尝试检索title属性的值时调用
namespace App; use Huslab\Elasticsearch\Model; class post extends Model { /** * Get the post title. * * @param string $value * @return string */ public function getTitleAttribute(string $value): string { return ucfirst($value); } }
如您所见,字段的原始值传递给访问器,允许您对其进行操作并返回值。要访问访问器的值,您只需在模型实例上访问title属性即可
$post = App\Post::find(1); $title = $post->title;
有时,您可能需要添加数组属性,这些属性在您的索引中没有相应的字段。为此,只需为该值定义一个访问器即可
public function getIsPublishedAttribute(): bool { return $this->attributes['status'] === 1; }
创建访问器后,只需将值添加到模型的appends属性中
protected $appends = ['is_published'];
一旦属性被添加到appends列表中,它将包含在模型的数组中。
定义一个修改器
要定义一个修改器,在模型上定义一个setFooAttribute方法,其中Foo是你想要访问的字段的“大写命名”形式。因此,让我们再次定义一个用于title属性的修改器。当我们尝试在模型上设置title属性值时,这个修改器将被自动调用
namespace App; use Huslab\Elasticsearch\Model; class post extends Model { /** * Set the post title. * * @param string $value * @return string */ public function setTitleAttribute(string $value): string { return strtolower($value); } }
修改器将接收正在设置的属性值,允许你操作该值,并将其操作后的值设置在模型内部$attributes属性上。例如,如果我们尝试将标题属性设置为Awesome post to read
$post = App\Post::find(1); $post->title = 'Awesome post to read';
在这个例子中,setTitleAttribute函数将使用值Awesome post to read被调用。然后修改器将应用tolower函数到名称上,并将结果值设置在内部$attributes数组中。
禁用事件
有时你可能需要暂时“禁用”模型触发的事件。你可以使用withoutEvents方法实现这一点。withoutEvents方法只接受一个闭包作为其唯一参数。在这个闭包中执行的任何代码都不会触发模型事件。例如,以下示例将获取并删除一个App\Models\Post实例而不触发任何模型事件。闭包返回的任何值都将由withoutEvents方法返回
use App\Models\Post; $post = Post::withoutEvents(function () use () { Post::findOrFail(1)->delete(); return Post::find(2); });
无事件保存单个模型
有时你可能希望“保存”给定的模型而不触发任何事件。你可以使用saveQuietly方法实现这一点
$post = Post::findOrFail(1); $post->title = 'Other search strategies'; $post->saveQuietly();
复制模型
你可以使用replicate方法创建现有模型实例的未保存副本。此方法在具有许多相同属性的模型实例非常有用
use App\Models\Address; $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001', ]); $billing = $shipping->replicate()->fill([ 'type' => 'billing' ]); $billing->save();
转换器和铸造
访问器、修改器和属性转换允许你在检索或设置模型实例上的Elasticsearch属性值时转换属性值。例如,你可能想使用Laravel加密器在存储在索引中时加密一个值,然后在访问Elasticsearch模型时自动解密该属性。或者,你可能想将存储在索引中的JSON字符串转换为数组,当通过Elasticsearch模型访问时。
访问器和修改器
定义访问器
访问器在访问时转换Elasticsearch属性值。要定义访问器,在你的模型上创建一个get{Attribute}Attribute方法,其中{Attribute}是你想要访问的字段的“大写命名”形式。
在这个例子中,我们将定义一个用于first_name属性的访问器。Elasticsearch将自动在尝试检索first_name属性值时调用访问器
namespace App\Models; use Huslab\Elasticsearch\Model; class User extends Model { /** * Get the user's first name. * * @param string $value * @return string */ public function getFirstNameAttribute(string $value): string { return ucfirst($value); } }
如你所见,字段的原始值传递给访问器,允许你操作并返回值。要访问访问器的值,你可以在模型实例上简单地访问first_name属性
use App\Models\User; $user = User::find(1); $firstName = $user->first_name;
你不仅限于在访问器中与单个属性交互。你也可以使用访问器从现有属性返回新的、计算出的值
/** * Get the user's full name. * * @return string */ public function getFullNameAttribute(): string { return "{$this->first_name} {$this->last_name}"; }
定义一个修改器
修改器在设置时转换Elasticsearch属性值。要定义修改器,在模型上定义一个set{Attribute}Attribute方法,其中{Attribute}是你想要访问的字段的“大写命名”形式。
让我们为first_name属性定义一个修改器。当我们尝试在模型上设置first_name属性值时,这个修改器将被自动调用
namespace App\Models; use Huslab\Elasticsearch\Model; class User extends Model { /** * Set the user's first name. * * @param string $value * @return void */ public function setFirstNameAttribute(string $value): void { $this->attributes['first_name'] = strtolower($value); } }
修改器将接收正在设置的属性值,允许你操作该值,并将其操作后的值设置在Elasticsearch模型内部$attributes属性上。要使用我们的修改器,我们只需要在Elasticsearch模型上设置first_name属性
use App\Models\User; $user = User::find(1); $user->first_name = 'Sally';
在这个例子中,将使用值 Sally 调用 setFirstNameAttribute 函数。然后,修改器将应用 strtolower 函数到名字上,并将结果值设置在内部的 $attributes 数组中。
属性转换
属性转换提供了类似于访问器和修改器的功能,而无需在模型上定义任何额外的函数。相反,您的模型的 $casts 属性提供了一种方便的方法来将属性转换为常见的数据类型。
$casts 属性应该是一个数组,其中键是要转换的属性的名称,值是你希望转换的字段的类型。支持转换的类型有
数组布尔值集合日期日期时间十进制:双精度浮点数加密加密:数组加密:集合加密:对象浮点数整数对象实数字符串时间戳
为了演示属性转换,让我们将 is_admin 属性转换为布尔值,该属性在我们的索引中存储为整数(0 或 1)。
namespace App\Models; use Huslab\Elasticsearch\Model; class User extends Model { /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'is_admin' => 'boolean', ]; }
定义转换后,当您访问 is_admin 属性时,它总是会转换为布尔值,即使其底层值存储在索引中为整数。
$user = App\Models\User::find(1); if ($user->is_admin) { // }
注意:为
null的属性不会进行转换。
日期转换
您可以通过在模型中的 $casts 属性数组中定义它们来转换日期属性。通常,日期应该使用 datetime 转换进行转换。
当定义 date 或 datetime 转换时,您还可以指定日期的格式。此格式将在模型序列化为数组或 JSON 时使用。
/** * The attributes that should be cast. * * @var array */ protected $casts = [ 'created_at' => 'datetime:Y-m-d', ];
当一个字段被转换为日期时,您可以将其值设置为 UNIX 时间戳、日期字符串(Y-m-d)、日期时间字符串或 DateTime / Carbon 实例。日期的值将被正确转换并存储在您的索引中。
您可以通过在模型上定义 serializeDate 方法来自定义所有模型日期的默认序列化格式。此方法不会影响您的日期如何格式化以存储在索引中。
/** * Prepare a date for array / JSON serialization. * * @param \DateTimeInterface $date * @return string */ protected function serializeDate(DateTimeInterface $date) { return $date->format('Y-m-d'); }
为了指定实际存储模型日期在索引中时应使用的格式,您应在模型上定义一个 $dateFormat 属性。
/** * The storage format of the model's date fields. * * @var string */ protected $dateFormat = 'U';
自定义转换
Laravel 有许多内置的有用的转换类型;然而,您有时可能需要定义自己的转换类型。您可以通过定义一个实现 CastsAttributes 接口的类来完成此操作。
实现此接口的类必须定义一个 get 和 set 方法。get 方法负责将来自索引的原始值转换为转换值,而 set 方法应将转换值转换为可以存储在索引中的原始值。作为一个例子,我们将重新实现内置的 json 转换类型作为自定义转换类型。
注意:由于类型不兼容,您将需要为 Eloquent 和 Elasticsearch 模型使用不同的转换,或者省略参数类型。
namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; class Json implements CastsAttributes { /** * Cast the given value. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param mixed $value * @param array $attributes * @return array */ public function get($model, $key, $value, $attributes) { return json_decode($value, true); } /** * Prepare the given value for storage. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param array $value * @param array $attributes * @return string */ public function set($model, $key, $value, $attributes) { return json_encode($value); } }
一旦您定义了一个自定义转换类型,您就可以使用其类名将其附加到模型属性上。
namespace App\Models; use App\Casts\Json; use Huslab\Elasticsearch\Model; class User extends Model { /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'options' => Json::class, ]; }
值对象转换
您不仅可以将值转换为原始类型。您还可以将值转换为对象。定义将值转换为对象的自定义转换与转换为原始类型非常相似;然而,set 方法应返回一个键/值对的数组,这些键/值将被用于在模型上设置原始的、可存储的值。
例如,我们将定义一个自定义转换类,将多个模型值转换为单个 Address 值对象。我们假设 Address 值对象有两个公共属性:lineOne 和 lineTwo
namespace App\Casts; use App\Models\Address as AddressModel; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use InvalidArgumentException; class Address implements CastsAttributes { /** * Cast the given value. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param mixed $value * @param array $attributes * @return \App\Models\Address */ public function get($model, $key, $value, $attributes) { return new AddressModel( $attributes['address_line_one'], $attributes['address_line_two'] ); } /** * Prepare the given value for storage. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param \App\Models\Address $value * @param array $attributes * @return array */ public function set($model, $key, $value, $attributes) { if (! $value instanceof AddressModel) { throw new InvalidArgumentException('The given value is not an Address instance.'); } return [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ]; } }
当将值对象转换为时,对值对象所做的任何更改将自动在模型保存之前同步回模型
use App\Models\User; $user = User::find(1); $user->address->lineOne = 'Updated Address Value'; $user->save();
提示:如果您计划将包含值对象的 Elasticsearch 模型序列化为 JSON 或数组,则应在值对象上实现
Illuminate\Contracts\Support\Arrayable和JsonSerializable接口。
数组 / JSON 序列化
当使用 toArray 和 toJson 方法将 Elasticsearch 模型转换为数组或 JSON 时,您的自定义转换值对象通常也会被序列化,只要它们实现了 Illuminate\Contracts\Support\Arrayable 和 JsonSerializable 接口。但是,当使用第三方库提供的值对象时,您可能无法将这些接口添加到对象中。
因此,您可以指定自定义转换类将负责序列化值对象。为此,您的自定义类转换应实现 Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 接口。该接口声明您的类应包含一个 serialize 方法,该方法应返回您的值对象的序列化形式
/** * Get the serialized representation of the value. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param mixed $value * @param array $attributes * @return mixed */ public function serialize($model, string $key, $value, array $attributes) { return (string) $value; }
入站转换
有时,您可能需要编写一个自定义转换,它仅转换模型上设置的值,而在从模型检索属性时不会执行任何操作。一个仅入站的经典示例是“哈希”转换。仅入站自定义转换应实现 CastsInboundAttributes 接口,该接口只需定义一个 set 方法。
namespace App\Casts; use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; class Hash implements CastsInboundAttributes { /** * The hashing algorithm. * * @var string */ protected $algorithm; /** * Create a new cast class instance. * * @param string|null $algorithm * @return void */ public function __construct($algorithm = null) { $this->algorithm = $algorithm; } /** * Prepare the given value for storage. * * @param \Illuminate\Database\Eloquent\Model|\Huslab\Elasticsearch\Model $model * @param string $key * @param array $value * @param array $attributes * @return string */ public function set($model, $key, $value, $attributes) { return is_null($this->algorithm) ? bcrypt($value) : hash($this->algorithm, $value); } }
转换参数
将自定义转换附加到模型时,可以通过使用冒号字符分隔并使用逗号分隔多个参数来指定转换参数。这些参数将被传递到转换类的构造函数中
/** * The attributes that should be cast. * * @var array */ protected $casts = [ 'secret' => Hash::class.':sha256', ];
可转换对象
您可能希望允许应用程序的值对象定义它们自己的自定义转换类。您可以将值对象类附加到实现 Illuminate\Contracts\Database\Eloquent\Castable 接口的对象上,而不是将自定义转换类附加到模型上。
use App\Models\Address; protected $casts = [ 'address' => Address::class, ];
实现 Castable 接口的对象必须定义一个 castUsing 方法,该方法应返回负责将 Castable 类转换为和从转换的自定义转换器类的类名
namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Castable; use App\Casts\Address as AddressCast; class Address implements Castable { /** * Get the name of the caster class to use when casting from / to this cast target. * * @param array $arguments * @return string */ public static function castUsing(array $arguments): string { return AddressCast::class; } }
当使用 Castable 类时,您仍然可以在 $casts 定义中提供参数。这些参数将被传递到 castUsing 方法
use App\Models\Address; protected $casts = [ 'address' => Address::class.':argument', ];
可转换对象与匿名转换类
通过结合“可转换对象”与 PHP 的 匿名类,您可以定义一个值对象及其转换逻辑作为一个单一的转换对象。为此,从值对象的 castUsing 方法返回一个匿名类。该匿名类应实现 CastsAttributes 接口
namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; class Address implements Castable { // ... /** * Get the caster class to use when casting from / to this cast target. * * @param array $arguments * @return object|string */ public static function castUsing(array $arguments) { return new class implements CastsAttributes { public function get($model, $key, $value, $attributes) { return new Address( $attributes['address_line_one'], $attributes['address_line_two'] ); } public function set($model, $key, $value, $attributes) { return [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ]; } }; } }
路由模型绑定
当将模型 ID 注入到路由或控制器操作中时,您通常会查询 Elasticsearch 索引以检索与该 ID 对应的模型。Laravel 路由模型绑定提供了一个方便的方式来自动将模型实例直接注入到路由中。例如,您可以直接注入与给定 ID 匹配的用户模型实例,而不是注入用户 ID。
隐式绑定
Laravel 自动解析在路由或控制器操作中定义的 Elasticsearch 模型,其类型提示变量名与路由段名称匹配。例如
use App\Models\Post; Route::get('/posts/{post}', function (Post $post) { return $post->content; });
由于$post变量被类型提示为App\Models\Post Elasticsearch模型,并且变量名与{post} URI段匹配,Laravel将自动注入具有与请求URI中相应值匹配的ID的模型实例。如果在数据库中找不到匹配的模型实例,将自动生成一个404 HTTP响应。
当然,在控制器方法中使用时,也可以进行隐式绑定。再次注意,{post} URI段与控制器中的$post变量匹配,该变量包含一个App\Models\Post类型提示
use App\Http\Controllers\PostController; use App\Models\Post; // Route definition... Route::get('/posts/{post}', [PostController::class, 'show']); // Controller method definition... public function show(Post $post): View { return view('post.full', ['post' => $post]); }
自定义键
有时您可能希望使用除_id之外的字段来解析Elasticsearch模型。为此,您可以在路由参数定义中指定该字段
use App\Models\Post; Route::get('/posts/{post:slug}', fn(Post $post): Post => $post);
如果您希望在检索给定的模型类时,模型绑定始终使用除_id之外的其他索引字段,您可以在Elasticsearch模型上覆盖getRouteKeyName方法
/** * Get the route key for the model. * * @return string */ public function getRouteKeyName(): string { return 'slug'; }
自定义缺失模型行为
通常,如果找不到隐式绑定的模型,将生成一个404 HTTP响应。但是,您可以通过在定义路由时调用缺失方法来自定义此行为。缺失方法接受一个闭包,如果找不到隐式绑定的模型,则将调用该闭包
use App\Http\Controllers\LocationsController; use Illuminate\Http\Request; Route::get('/locations/{location:slug}', [LocationsController::class, 'show']) ->missing(fn(Request $request) => Redirect::route('locations.index') ->name('locations.view');
显式绑定
您不必使用Laravel的隐式、基于约定的模型解析来使用模型绑定。您还可以显式定义路由参数如何对应于模型。要注册显式绑定,请使用路由器的model方法指定给定参数的类。您应该在RouteServiceProvider类的boot方法开始时定义您的显式模型绑定
use App\Models\Post; use Illuminate\Support\Facades\Route; /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot():void { Route::model('post', Post::class); // ... }
接下来,定义一个包含{post}参数的路由
use App\Models\Post; Route::get('/posts/{post}', function (Post $post) { // ... });
由于我们已经将所有{post}参数绑定到App\Models\Post模型,因此将注入该类的实例。例如,对posts/1的请求将注入索引中ID为1的Post实例。
如果索引中没有找到匹配的模型实例,将自动生成一个404 HTTP响应。
自定义解析逻辑
如果您希望定义自己的模型绑定解析逻辑,可以使用Route::bind方法。您传递给bind方法的闭包将接收URI段的值,并应返回应注入到路由的类的实例。同样,这种自定义应发生在应用程序的RouteServiceProvider的boot方法中
use App\Models\Post; use Illuminate\Support\Facades\Route; /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot(): void { Route::bind('post', function (string $value): Post { return Post::where('title', $value)->firstOrFail(); }); // ... }
或者,您可以在Elasticsearch模型上覆盖resolveRouteBinding方法。此方法将接收URI段的值,并应返回应注入到路由的类的实例
/** * Retrieve the model for a bound value. * * @param mixed $value * @param string|null $field * @return \Huslab\Elasticsearch\Model|null */ public function resolveRouteBinding($value, ?string $field = null): ?self { return $this->where('name', $value)->firstOrFail(); }
如果路由正在使用隐式绑定范围,将使用resolveChildRouteBinding方法来解决父模型的子绑定
/** * Retrieve the child model for a bound value. * * @param string $childType * @param mixed $value * @param string|null $field * @return \Huslab\Elasticsearch\Model|null */ public function resolveChildRouteBinding(string $childType, $value, ?string $field): ?self { return parent::resolveChildRouteBinding($childType, $value, $field); }
作为查询构建器使用
您可以使用ES外观在任何地方直接访问查询构建器。
创建新的索引
ES::create('my_index'); # or ES::index('my_index')->create();
使用自定义选项创建索引(可选)
use Huslab\Elasticsearch\Facades\ES; use Huslab\Elasticsearch\Index; ES::index('my_index')->create(function(Index $index) { $index->shards(5)->replicas(1)->mapping([ 'my_type' => [ 'properties' => [ 'first_name' => [ 'type' => 'string', ], 'age' => [ 'type' => 'integer' ] ] ] ]) }); # or ES::create('my_index', function(Index $index){ $index->shards(5)->replicas(1)->mapping([ 'my_type' => [ 'properties' => [ 'first_name' => [ 'type' => 'string', ], 'age' => [ 'type' => 'integer' ] ] ] ]) });
删除索引
ES::drop("my_index"); # or ES::index("my_index")->drop();
运行查询
运行查询时,首先(可选)选择连接和索引。
$documents = ES::connection("default") ->index("my_index") ->type("my_type") ->get(); # return a collection of results
您可以将上述查询缩短为
$documents = ES::type("my_type")->get(); # return a collection of results
在查询中显式设置连接或索引名称将覆盖config/es.php中的配置。
通过ID获取文档
ES::type("my_type")->id(3)->first(); # or ES::type("my_type")->_id(3)->first();
排序
ES::type("my_type")->orderBy("created_at", "desc")->get(); # Sorting with text search score ES::type("my_type")->orderBy("_score")->get();
限制和偏移量
ES::type("my_type")->take(10)->skip(5)->get();
仅选择特定字段
ES::type("my_type")->select("title", "content")->take(10)->skip(5)->get();
WHERE子句
ES::type("my_type")->where("status", "published")->get(); # or ES::type("my_type")->where("status", "=", "published")->get();
WHERE大于
ES::type("my_type")->where("views", ">", 150)->get();
WHERE大于等于
ES::type("my_type")->where("views", ">=", 150)->get();
WHERE小于
ES::type("my_type")->where("views", "<", 150)->get();
WHERE小于等于
ES::type("my_type")->where("views", "<=", 150)->get();
WHERE模糊匹配
ES::type("my_type")->where("title", "like", "foo")->get();
WHERE字段存在
ES::type("my_type")->where("hobbies", "exists", true)->get(); # or ES::type("my_type")->whereExists("hobbies", true)->get();
WHERE IN子句
ES::type("my_type")->whereIn("id", [100, 150])->get();
WHERE BETWEEN子句
ES::type("my_type")->whereBetween("id", 100, 150)->get(); # or ES::type("my_type")->whereBetween("id", [100, 150])->get();
WHERE NOT子句
ES::type("my_type")->whereNot("status", "published")->get(); # or ES::type("my_type")->whereNot("status", "=", "published")->get();
WHERE NOT大于
ES::type("my_type")->whereNot("views", ">", 150)->get();
当不大于或等于时
ES::type("my_type")->whereNot("views", ">=", 150)->get();
当不小于时
ES::type("my_type")->whereNot("views", "<", 150)->get();
当不小于或等于时
ES::type("my_type")->whereNot("views", "<=", 150)->get();
当不匹配时
ES::type("my_type")->whereNot("title", "like", "foo")->get();
当字段不存在时
ES::type("my_type")->whereNot("hobbies", "exists", true)->get(); # or ES::type("my_type")->whereExists("hobbies", true)->get();
当不在子句中时
ES::type("my_type")->whereNotIn("id", [100, 150])->get();
当不在子句之间时
ES::type("my_type")->whereNotBetween("id", 100, 150)->get(); # or ES::type("my_type")->whereNotBetween("id", [100, 150])->get();
通过地理点的距离进行搜索
ES::type("my_type")->distance("location", ["lat" => -33.8688197, "lon" => 151.20929550000005], "10km")->get(); # or ES::type("my_type")->distance("location", "-33.8688197,151.20929550000005", "10km")->get(); # or ES::type("my_type")->distance("location", [151.20929550000005, -33.8688197], "10km")->get();
使用数组查询进行搜索
ES::type("my_type")->body([ "query" => [ "bool" => [ "must" => [ [ "match" => [ "address" => "mill" ] ], [ "match" => [ "address" => "lane" ] ] ] ] ] ])->get(); # Note that you can mix between query builder and array queries. # The query builder will will be merged with the array query. ES::type("my_type")->body([ "_source" => ["content"] "query" => [ "bool" => [ "must" => [ [ "match" => [ "address" => "mill" ] ] ] ] ], "sort" => [ "_score" ] ])->select("name")->orderBy("created_at", "desc")->take(10)->skip(5)->get(); # The result query will be /* Array ( [index] => my_index [type] => my_type [body] => Array ( [_source] => Array ( [0] => content [1] => name ) [query] => Array ( [bool] => Array ( [must] => Array ( [0] => Array ( [match] => Array ( [address] => mill ) ) ) ) ) [sort] => Array ( [0] => _score [1] => Array ( [created_at] => desc ) ) ) [from] => 5 [size] => 10 [client] => Array ( [ignore] => Array ( ) ) ) */
搜索整个文档
ES::type("my_type")->search("hello")->get(); # search with Boost = 2 ES::type("my_type")->search("hello", 2)->get(); # search within specific fields with different weights ES::type("my_type")->search("hello", function($search){ $search->boost(2)->fields(["title" => 2, "content" => 1]) })->get();
使用高亮字段进行搜索
$doc = ES::type("my_type")->highlight("title")->search("hello")->first(); # Multiple fields Highlighting is allowed. $doc = ES::type("my_type")->highlight("title", "content")->search("hello")->first(); # Return all highlights as array using $doc->getHighlights() method. $doc->getHighlights(); # Also you can return only highlights of specific field. $doc->getHighlights("title");
仅返回第一个文档
ES::type("my_type")->search("hello")->first();
仅返回计数
ES::type("my_type")->search("hello")->count();
扫描和滚动查询
# These queries are suitable for large amount of data. # A scrolled search allows you to do an initial search and to keep pulling batches of results # from Elasticsearch until there are no more results left. # It’s a bit like a cursor in a traditional database $documents = ES::type("my_type")->search("hello") ->scroll("2m") ->take(1000) ->get(); # Response will contain a hashed code `scroll_id` will be used to get the next result by running $documents = ES::type("my_type")->search("hello") ->scroll("2m") ->scrollID("DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3") ->get(); # And so on ... # Note that you don't need to write the query parameters in every scroll. All you need the `scroll_id` and query scroll time. # To clear `scroll_id` ES::type("my_type")->scrollID("DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3") ->clear();
每页5个文档进行分页
$documents = ES::type("my_type")->search("hello")->paginate(5); # Getting pagination links $documents->links(); # Bootstrap 4 pagination $documents->links("bootstrap-4"); # Simple bootstrap 4 pagination $documents->links("simple-bootstrap-4"); # Simple pagination $documents->links("simple-default");
这些都是您可以使用的所有分页方法
$documents->count() $documents->currentPage() $documents->firstItem() $documents->hasMorePages() $documents->lastItem() $documents->lastPage() $documents->nextPageUrl() $documents->perPage() $documents->previousPageUrl() $documents->total() $documents->url($page)
在不执行的情况下获取查询数组
ES::type("my_type")->search("hello")->where("views", ">", 150)->query();
获取原始Elasticsearch响应
ES::type("my_type")->search("hello")->where("views", ">", 150)->response();
忽略错误的HTTP响应
ES::type("my_type")->ignore(404, 500)->id(5)->first();
查询缓存(Laravel & Lumen)
该软件包包含基于laravel缓存的内置缓存层。
ES::type("my_type")->search("hello")->remember(10)->get(); # Specify a custom cache key ES::type("my_type")->search("hello")->remember(10, "last_documents")->get(); # Caching using other available driver ES::type("my_type")->search("hello")->cacheDriver("redis")->remember(10, "last_documents")->get(); # Caching with cache key prefix ES::type("my_type")->search("hello")->cacheDriver("redis")->cachePrefix("docs")->remember(10, "last_documents")->get();
执行Elasticsearch原生查询
ES::raw()->search([ "index" => "my_index", "type" => "my_type", "body" => [ "query" => [ "bool" => [ "must" => [ [ "match" => [ "address" => "mill" ] ], [ "match" => [ "address" => "lane" ] ] ] ] ] ] ]);
插入新文档
ES::type("my_type")->id(3)->insert([ "title" => "Test document", "content" => "Sample content" ]); # A new document will be inserted with _id = 3. # [id is optional] if not specified, a unique hash key will be generated.
一次性批量插入多个文档。
# Main query ES::index("my_index")->type("my_type")->bulk(function ($bulk){ # Sub queries $bulk->index("my_index_1")->type("my_type_1")->id(10)->insert(["title" => "Test document 1","content" => "Sample content 1"]); $bulk->index("my_index_2")->id(11)->insert(["title" => "Test document 2","content" => "Sample content 2"]); $bulk->id(12)->insert(["title" => "Test document 3", "content" => "Sample content 3"]); }); # Notes from the above query: # As index and type names are required for insertion, Index and type names are extendable. This means that: # If index() is not specified in subquery: # -- The builder will get index name from the main query. # -- if index is not specified in main query, the builder will get index name from configuration file. # And # If type() is not specified in subquery: # -- The builder will get type name from the main query. # you can use old bulk code style using multidimensional array of [id => data] pairs ES::type("my_type")->bulk([ 10 => [ "title" => "Test document 1", "content" => "Sample content 1" ], 11 => [ "title" => "Test document 2", "content" => "Sample content 2" ] ]); # The two given documents will be inserted with its associated ids
更新现有文档
ES::type("my_type")->id(3)->update([ "title" => "Test document", "content" => "sample content" ]); # Document has _id = 3 will be updated. # [id is required]
# Bulk update ES::type("my_type")->bulk(function ($bulk){ $bulk->id(10)->update(["title" => "Test document 1","content" => "Sample content 1"]); $bulk->id(11)->update(["title" => "Test document 2","content" => "Sample content 2"]); });
递增字段
ES::type("my_type")->id(3)->increment("views"); # Document has _id = 3 will be incremented by 1. ES::type("my_type")->id(3)->increment("views", 3); # Document has _id = 3 will be incremented by 3. # [id is required]
递减字段
ES::type("my_type")->id(3)->decrement("views"); # Document has _id = 3 will be decremented by 1. ES::type("my_type")->id(3)->decrement("views", 3); # Document has _id = 3 will be decremented by 3. # [id is required]
使用脚本进行更新
# increment field by script ES::type("my_type")->id(3)->script( "ctx._source.$field += params.count", ["count" => 1] ); # add php tag to tags array list ES::type("my_type")->id(3)->script( "ctx._source.tags.add(params.tag)", ["tag" => "php"] ); # delete the doc if the tags field contain mongodb, otherwise it does nothing (noop) ES::type("my_type")->id(3)->script( "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }", ["tag" => "mongodb"] );
删除文档
ES::type("my_type")->id(3)->delete(); # Document has _id = 3 will be deleted. # [id is required]
# Bulk delete ES::type("my_type")->bulk(function ($bulk){ $bulk->id(10)->delete(); $bulk->id(11)->delete(); });
版本
请参阅发布页面。
作者
Basem Khirat - basemkhirat@gmail.com - @basemkhirat
Moritz Friedrich - moritz@Huslab.com
错误、建议和贡献
感谢所有为原始项目做出贡献的人,以及其他为这个分支做出贡献的人!
请使用Github来报告错误、发表评论或提出建议。
如果您有兴趣帮忙,最紧迫的问题将是使查询构建器现代化,以更好地支持Elasticsearch功能,以及完成测试套件!
许可证
MIT
祝您搜索愉快。