diff --git a/.gitignore b/.gitignore index 08df105..a311532 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Thumbs.db .env .php-cs-fixer.cache .phpunit.result.cache +.phpunit.cache/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 47a69ba..6bc47da 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -8,4 +8,5 @@ '@PSR12' => true, 'no_unused_imports' => true, ]) - ->setFinder($finder); + ->setFinder($finder) + ->setUnsupportedPhpVersionAllowed(true); diff --git a/CLAUDE.md b/CLAUDE.md index 61b6b43..4a3e1d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,6 +36,28 @@ Scope 可选:dsl / index / agg / query / docs。Breaking change 加 `!` 后缀 PSR-5 规范。 +## 待办 + +- [x] **clone query 后 try-finally 恢复**:first() / paginate() 等方法 clone query 后修改状态,需 try-finally 确保恢复 +- [x] **ClausesSupport API 统一**:must()/should()/filter() 等改为追加语义,移除 addXXX() 方法 +- [x] **hasMore() 确认无 bug**:scroll 场景 !empty(hits) 正确,分页应用 page()` 按批 yield Results;`cursor($duration): Generator` 逐条 yield 完整 hit(_id/_score/_source,yield from chunk 的 hits、复用其 finally clear);保留 `scroll()/next()/clear()` 作低层原语。底层未来可换 PIT+search_after(上层签名不变) + ## 测试 测试在 Docker 容器中运行,需要设置以下环境变量: @@ -45,10 +67,14 @@ PSR-5 规范。 | `PHP_CONTAINER` | Docker 容器名 | | `PROJECT_PATH` | 项目在容器内的路径 | | `PROXY_PORT` | HTTP 代理端口(推送用) | +| `ELASTICKIT_TEST_HOST` | ES 集成测试地址(如 `https://localhost:9200`),不设置则跳过集成测试 | + +## 推送代码前需要执行4件套 ```bash docker exec $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpunit --testsuite unit" docker exec $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpunit --testsuite index" +docker exec -e ELASTICKIT_TEST_HOST=https://elasticsearch:9200 $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpunit --testsuite integration" docker exec $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpunit" docker exec $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpstan analyse" docker exec $PHP_CONTAINER sh -c "cd $PROJECT_PATH && vendor/bin/phpmd src text phpmd.xml" diff --git a/README.md b/README.md index 85a0b34..d972849 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ PHP Elasticsearch DSL 查询构建库,覆盖查询、聚合、CRUD、批量写 composer require ykan/elastickit:^8 ``` -> 需要 PHP 8.1+、Elasticsearch 8.x。依赖 `elasticsearch-php` 自动安装。ES 7.x 用户见 [7.x 分支](https://github.com/ykan821/ElasticKit/tree/7.x)。 +> 需要 PHP 8.1+、Elasticsearch 8.x。依赖 `elasticsearch-php` 自动安装。 ## 快速开始 @@ -27,8 +27,8 @@ Index::setClient($client); // 2. 定义索引 class ProductIndex extends Index { - protected $name = 'products'; - protected $mappings = [ + protected string $name = 'products'; + protected array $mappings = [ 'properties' => [ 'title' => ['type' => 'text'], 'price' => ['type' => 'float'], @@ -79,7 +79,7 @@ $bool = Boolean::create() // 增量构建 if ($filterByPrice) { - $bool->addFilter(Range::create('price', [10, 100])); + $bool->filter(Range::create('price', [10, 100])); } $query = Query::create($bool); @@ -178,18 +178,24 @@ $results->lastPage(); $results->items(); $results->toPaginator(); // 转为框架分页器(需注册 Paginator Resolver) -// 游标遍历(大批量导出) -foreach (ProductIndex::query()->cursor() as $batch) { - foreach ($batch->docs() as $doc) { +// 分批遍历(大批量导出/批处理,每次 yield 一个 Results) +foreach (ProductIndex::query()->chunk() as $results) { + foreach ($results->docs() as $doc) { // ... } } + +// 逐条遍历(导出/逐条加工,每次 yield 一个 hit:_id/_score/_source) +foreach (ProductIndex::query()->cursor() as $hit) { + $doc = $hit['_source']; + // ... +} ``` ### 文档 CRUD ```php -ProductIndex::insert(1, ['title' => 'Hello', 'price' => 99.9]); +ProductIndex::doc(1)->save(['title' => 'Hello', 'price' => 99.9]); $doc = ProductIndex::doc(1); $doc->source(); // ['title' => 'Hello', 'price' => 99.9] @@ -277,7 +283,6 @@ ProductIndex::listen('search.*', function (Event $e) { - [实践指南](docs/guide.md)——电商订单场景,从安装到上线的完整流程 - [Index 文档](docs/index.md)——搜索、CRUD、批量操作、零停机重建、事件 -- [升级指南](docs/upgrade.md)——v7.x → v8.x 迁移说明 - [更新日志](CHANGELOG.md) - [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html)——查询类型和参数参考 diff --git a/composer.json b/composer.json index 058f8b6..ec1cc09 100644 --- a/composer.json +++ b/composer.json @@ -33,8 +33,8 @@ ], "scripts": { "analyse": "phpstan analyse", - "cs-check": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --dry-run --diff", - "cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix" + "cs-check": "php-cs-fixer fix --dry-run --diff", + "cs-fix": "php-cs-fixer fix" }, "config": { "audit": { diff --git a/docs/guide.md b/docs/guide.md index b95f499..3ca10a5 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -40,9 +40,9 @@ use Illuminate\Support\Facades\Db; class OrderIndex extends Index { - protected $name = 'orders'; + protected string $name = 'orders'; - protected $mappings = [ + protected array $mappings = [ 'properties' => [ 'order_no' => ['type' => 'keyword'], // 精确匹配 'status' => ['type' => 'keyword'], // pending/paid/shipped/completed @@ -128,17 +128,17 @@ class OrderIndex extends Index // 精确筛选(不需要评分,放 filter) if (!empty($filters['status'])) { - $bool->addFilter(Term::create('status', $filters['status'])); + $bool->filter(Term::create('status', $filters['status'])); } if (!empty($filters['start_date']) && !empty($filters['end_date'])) { - $bool->addFilter(Range::create('created_at', [$filters['start_date'], $filters['end_date']])); + $bool->filter(Range::create('created_at', [$filters['start_date'], $filters['end_date']])); } // 关键词搜索(OR,放 should) if (!empty($filters['keyword'])) { - $bool->addShould(Wildcard::create('order_no', "*{$filters['keyword']}*")); - $bool->addShould(Wildcard::create('merchant_name', "*{$filters['keyword']}*")); + $bool->should(Wildcard::create('order_no', "*{$filters['keyword']}*")); + $bool->should(Wildcard::create('merchant_name', "*{$filters['keyword']}*")); } return static::query(Query::create($bool)); @@ -160,7 +160,7 @@ public function index(Request $request) } ``` -> 条件用 `if` 逐个判断,只有传了值才加查询。`addShould()` 实现 OR 搜索。深分页场景用 `cursor()` 替代 `paginate()`。 +> 条件用 `if` 逐个判断,只有传了值才加查询。`should()` 实现 OR 搜索。深分页场景用 `chunk()`(按批)或 `cursor()`(逐条)替代 `paginate()`。 运营还要导出筛选结果到 Excel。ES 默认 `max_result_window = 10000`,`from/size` 翻不到后面的数据,用 `cursor()` 基于 scroll 遍历: @@ -169,8 +169,8 @@ public function export(array $filters) { $search = static::searchOrders($filters)->sort('created_at', 'desc'); - foreach ($search->cursor() as $batch) { - foreach ($batch->docs() as $doc) { + foreach ($search->chunk() as $results) { + foreach ($results->docs() as $doc) { // 写入 Excel } } diff --git a/docs/index.md b/docs/index.md index 996048e..c1811bd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,18 +25,18 @@ Index::setClient($logClient, 'logs'); ```php class ProductIndex extends Index { - protected $name = 'products'; // 索引名(必填) - protected $mappings = [ // 索引 mappings + protected string $name = 'products'; // 索引名(必填) + protected array $mappings = [ // 索引 mappings 'properties' => [ 'title' => ['type' => 'text'], 'price' => ['type' => 'float'], 'status' => ['type' => 'keyword'], ], ]; - protected $settings = [ // 索引 settings + protected array $settings = [ // 索引 settings 'number_of_shards' => 1, ]; - protected $connection = 'main'; // 连接名(默认 'default') + protected string $connection = 'main'; // 连接名(默认 'default') public function rebuildName(): string // 重建后的真实索引名(可重写自定义) { @@ -116,17 +116,27 @@ while (count($results->docs()) > 0) { ProductIndex::query()->clear($scrollId); ``` -## Cursor +## Chunk / Cursor -Cursor 把 scroll 封装成 PHP 生成器: +把 scroll 封装成 PHP 生成器,scroll 自动清理。 + +**chunk** 按批遍历,每次 yield 一个 Results(含 docs/hits/total 等): ```php -foreach (ProductIndex::query()->cursor() as $results) { +foreach (ProductIndex::query()->chunk() as $results) { foreach ($results->docs() as $doc) { // 处理 } } -// scroll 自动清理 +``` + +**cursor** 逐条遍历,每次 yield 一个完整 hit(_id/_score/_source): + +```php +foreach (ProductIndex::query()->cursor() as $hit) { + $doc = $hit['_source']; + $id = $hit['_id']; +} ``` ## 文档 CRUD @@ -164,6 +174,28 @@ $bulk->delete(3); $bulk->execute(); // 执行所有操作,执行后清空状态 ``` +### 错误处理 + +`execute()` 默认在响应包含错误时抛出 `RuntimeException`。使用 `onError()` 自定义处理。回调接收 ES 原始响应,不抛出则继续,抛出则中断: + +```php +$bulk = new Bulk(new ProductIndex()); + +// 不设 onError → 有错误就抛异常 +$bulk->execute(); + +// 设 onError → 回调内不抛则继续,抛则中断 +$bulk->onError(function (array $response) { + $failures = count(array_filter($response['items'], fn($i) => isset($i['index']['error']))); + if ($failures > 100) { + throw new RuntimeException("失败超过阈值: {$failures}"); + } + Log::warning("部分失败: {$failures} 条"); +})->execute(); +``` + +> `batchSize` 自动 flush 时的错误同样走 `onError`,不会丢失。 + ## 零停机重建 创建新索引 → 导入数据 → 切换别名。 @@ -242,14 +274,12 @@ $rebuild->run(['after' => '2024-01-01']); ### 错误处理 -导入错误会触发 `rebuild.import.failed` 事件(始终),并抛出异常(默认)。使用 `skipErrors()` 抑制异常但保留事件通知: +Rebuild 内部使用 Bulk 执行导入,`onError()` 用法与 [批量操作 > 错误处理](#错误处理) 一致: ```php -Index::listen('rebuild.import.failed', function (Event $e) { - Log::warning("重建导入错误", $e->response); -}); - -$rebuild->skipErrors()->run(); // 通过事件记录错误,不中断 +$rebuild->onError(function (array $response) { + Log::warning("重建导入错误", $response); +})->run(); ``` > rebuild 期间 DB 仍在变更,新索引是开始时刻的快照,建议 rebuild 后通过 `updated_at` 增量同步补齐。 @@ -351,4 +381,3 @@ Index::setClient($client); | `manager.swap_alias.after` | `$response` | | `rebuild.run.before` | | | `rebuild.run.after` | `$newIndex`, `$oldIndex` | -| `rebuild.import.failed` | `$response` | diff --git a/docs/upgrade.md b/docs/upgrade.md deleted file mode 100644 index 66ec6d0..0000000 --- a/docs/upgrade.md +++ /dev/null @@ -1,94 +0,0 @@ -# 升级指南 - -## v7.x → v8.x - -### 环境要求 - -| | v7.x | v8.x | -|---|---|---| -| PHP | 7.2+ | **8.1+** | -| Elasticsearch | 7.x | **8.x** | -| elasticsearch-php | ^7.0 | **^8.0** | - -```bash -composer require ykan/elastickit:^8 -``` - -### Client 配置 - -命名空间从 `Elasticsearch\` 变更为 `Elastic\Elasticsearch\`: - -```php -// v7.x -$client = \Elasticsearch\ClientBuilder::create() - ->setHosts(['http://localhost:9200']) - ->build(); - -// v8.x -$client = \Elastic\Elasticsearch\ClientBuilder::create() - ->setHosts(['https://localhost:9200']) - ->setBasicAuthentication('elastic', 'password') - ->build(); -``` - -> ES 8.x 默认启用安全认证(TLS + 认证)。需使用 `https://` 并提供凭据。 - -### 不兼容变更 - -**1. 自定义 Client** - -`Client` 现在是 `final` 类——不可继承。使用 `ClientBuilder` 配置代替: - -```php -// v7.x — 继承 Client 自定义 -class MyClient extends \Elasticsearch\Client { ... } - -// v8.x — 通过 ClientBuilder 配置 -$client = \Elastic\Elasticsearch\ClientBuilder::create() - ->setHosts([...]) - ->setLogger($logger) - ->setSSLVerification(false) - ->build(); -``` - -**2. 响应对象** - -elasticsearch-php 8.x 返回 Response 对象而非数组。ElasticKit 内部已处理——Index 层所有方法(search、get、bulk 等)仍返回数组。**用户代码无需修改。** - -如果你在 ElasticKit 之外直接使用 elasticsearch-php 客户端: - -```php -// v7.x — 返回数组 -$response = $client->search($params); -$total = $response['hits']['total']['value']; - -// v8.x — 返回 Response 对象,使用 asArray() -$response = $client->search($params); -$total = $response['hits']['total']['value']; // ArrayAccess 仍可读取 -$total = $response->asArray()['hits']['total']['value']; // 显式转换 -``` - -**3. GeoPolygon 查询** - -`geo_polygon` 查询在 ES 8.x 中已移除。使用 `geo_shape` 代替: - -```php -// v7.x -$query->geoPolygon('location', $points); - -// v8.x -$query->geoShape('location', function ($s) { - $s->shape($polygon, 'envelope'); -}); -``` - -### 无需修改 - -以下在 v7.x 和 v8.x 中完全一致: - -- DSL 查询构建(match、term、bool、range 等) -- 聚合构建 -- Index 层所有 API(Search、Doc、Bulk、Manager、Rebuild) -- 事件系统 -- `Query::make()`、`Agg::make()`、`Query::when()` -- Index 子类定义($name、$mappings、$settings) diff --git "a/docs/\345\256\236\346\210\230\346\214\207\345\215\227\350\247\204\345\210\222.md" "b/docs/\345\256\236\346\210\230\346\214\207\345\215\227\350\247\204\345\210\222.md" deleted file mode 100644 index de3d7c6..0000000 --- "a/docs/\345\256\236\346\210\230\346\214\207\345\215\227\350\247\204\345\210\222.md" +++ /dev/null @@ -1,225 +0,0 @@ -# 实战指南规划 - -以电商订单系统为例,展示 ElasticKit 在真实业务中的完整使用流程。 - -> 灵感来源:真实生产环境的 ES 集成经验,简化后用于教学。 - -## 场景设定 - -电商后台,订单数据分散在多张表,需要搜索、筛选、统计、导出。核心需求: - -- **订单列表**:按状态、日期、金额、品类筛选,支持搜索订单号/商品名 -- **统计看板**:按月/品类统计销售额,按商家分组 -- **数据导出**:筛选结果批量导出 Excel -- **实时同步**:数据库变更后 ES 自动更新 - -### 技术难点 - -1. **数据分散在多张表**(订单 + 用户 + 商品 + 商家),写入时必须关联组装 -2. **订单有嵌套明细**(items),ES 用 nested 类型存储 -3. **筛选条件多且动态**(10+ 个可选条件),需要灵活的条件构建器 -4. **统计和列表要联动**(同一组条件,既要分页列表又要聚合数据) -5. **数据一致性**(数据库更新后 ES 必须同步,处理冲突重试) - ---- - -## 踩坑记录 - -> 从生产环境提炼。只收录非显而易见的实战教训,ES 基础知识不在此列。 - -### 数据建模 - -**1. nested 要慎重** - -nested 查询比普通字段慢很多。真实项目中用了 nested 存流水明细,后来条件查询改成了平铺字段,放弃了嵌套查询。教训:**如果不需要对嵌套文档做独立筛选,就不要用 nested**,平铺即可。 - -### 查询 - -**2. 聚合 N+1 反模式** - -多组统计数据(如按不同状态分别求和)不要发 N 次查询。用一次查询 + 多个 `filter` 聚合桶,或 `aggs()` 嵌套聚合。真实项目账单统计发了 11 次查询,其实可以合并为 1 次。 - -**3. 深分页 10000 限制** - -ES 默认 `max_result_window = 10000`。应对: - -- 用户翻页:限制最大页数 -- 批量导出:用 `cursor()`(scroll API) -- 注意 `chunk()` 如果内部用 from/size,同样受限 - -### 增量同步 - -**4. 队列投递要延迟** - -ORM afterSave 触发时,MySQL 事务可能还没提交。队列 job 立即执行会读到旧数据。解决:延迟投递,等事务提交。 - -**5. 队列 job 不要先删除再执行** - -先删除任务再执行 ES 更新,失败时任务就丢了。正确:执行成功后再删除。 - -**6. Bulk API 不支持 retry_on_conflict** - -ES 单文档 update 支持 `retry_on_conflict`,但 Bulk API 的 update action 不支持。并发写入版本冲突只能靠应用层重试。ElasticKit 的 `Bulk::retryOnConflict()` 封装了这个参数。 - -**7. 间接关联更新** - -商品改名了,订单里的 `product_name` 也要改。但订单 source() 查的是订单表,不是商品表。解决:商品变更 → 查关联订单 ID → 批量更新订单文档。 - -``` -商品表变更 → SELECT id FROM 订单表 WHERE product_id = ? → source(['ids' => $ids]) → Bulk update -``` - -**8. doc_as_upsert 默认策略** - -更新时统一用 `doc_as_upsert: true`(不存在则插入,存在则更新),避免区分 insert/update 两种逻辑。 - -**9. Canal 配置技巧** - -- **字段过滤**:只监听业务字段,忽略 `updated_at` 等无关变更 -- **事件过滤**:只监听 UPDATE,忽略 INSERT/DELETE -- **去重**:同一批次内相同文档 ID 合并,避免重复入队 - -### 数据一致性 - -**10. 一致性校验是必要的** - -不管同步多完善,总会丢数据(进程崩溃、网络超时、冲突重试耗尽)。定时对比 DB 和 ES 关键字段,不一致就修复。真实项目每小时跑一次。 - ---- - -## 阶段规划 - -### 阶段 1:安装与配置 - -**业务背景**:项目立项,先把 ES 连上。 - -**内容**: -- composer 安装 -- 注册 ES Client(Laravel ServiceProvider) -- 验证连接 - ---- - -### 阶段 2:设计索引 - -**业务背景**:订单数据分散在 4 张表,ES 不支持 join,写入时关联组装。 - -**内容**: -- OrderIndex 类定义 -- mappings 设计:基本字段 + nested(订单明细)+ 关联字段(用户/商家/品类) -- `source()` 方法:多表 join 组装文档数据 -- 增量查询支持:`source(['ids' => [...]])` - -**踩坑提醒**:坑 1(nested 慎重) - -**索引字段**(简化版,保留核心模式): - -``` -基本字段:id, order_no, user_id, merchant_id, status, total_amount, - paid_at, created_at, is_cancelled - -关联字段(写入时从其他表组装):user_name, merchant_name, category_name - -嵌套文档:items (product_id, product_name, price, quantity) -``` - ---- - -### 阶段 3:首次导入 - -**业务背景**:索引设计好了,把现有数据灌进去。 - -**内容**: -- Rebuild::run() 全量导入 -- batchSize 调优 -- 查看结果验证 - ---- - -### 阶段 4:搜索与筛选 - -**业务背景**:运营要一个订单查询页面,条件多且动态。 - -**内容**: -- 条件构建器模式(从真实代码提炼) -- `term`(精确匹配)/ `terms`(多值匹配)/ `range`(日期范围)/ `wildcard`(模糊搜索) -- `bool should` 实现多字段 OR 搜索 -- `when()` 条件查询 -- 分页与排序 - -**踩坑提醒**:坑 3(深分页限制) - ---- - -### 阶段 5:聚合统计 - -**业务背景**:管理看板需要按月/品类统计销售额,筛选条件和列表页联动。 - -**内容**: -- 聚合 + 列表一次查询(同一条件,`size(0)` 只取聚合) -- terms 桶聚合:按品类分组 -- date_histogram:按月分组,带时区和补零 -- stats/sum:金额统计 -- 嵌套聚合:品类 → 月份 → 销售额合计 - -**踩坑提醒**:坑 2(聚合 N+1) - ---- - -### 阶段 6:增量同步 - -**业务背景**:数据持续变更(发货、退款、改价),ES 要跟着更新。 - -**内容**: -- 同步流程:数据变更 → 提取 doc_ids → source(['ids']) 取最新数据 → Bulk update -- 队列投递与延迟 -- 冲突重试 -- 间接关联更新(商品改名 → 关联订单全部更新) -- doc_as_upsert 默认策略 - -**踩坑提醒**:坑 4-9 全部涉及 - ---- - -### 阶段 7:Schema 演进 - -**业务背景**:产品要加字段(如新增"配送方式"),需要修改 mapping。 - -**内容**: -- 新增可空字段:`putMapping()` 直接加 -- Breaking 变更(改字段类型、改分词器):必须 Rebuild -- 修改 `source()` 配合新字段 -- Rebuild 完整流程 - ---- - -### 阶段 8:监控与数据一致性 - -**业务背景**:上线后要监控搜索性能和同步延迟。 - -**内容**: -- 慢查询日志(事件监听) -- 数据一致性校验:定时对比 DB 和 ES 关键字段,不一致则重新入队 - -**踩坑提醒**:坑 10(一致性校验) - ---- - -## 覆盖的 ElasticKit 特性 - -| 阶段 | 特性 | -|---|---| -| 1 | 安装、注册 Client | -| 2 | Index 定义、mappings、nested、source()、multi-table join | -| 3 | Rebuild::run()、Bulk、别名切换 | -| 4 | term/terms/range/wildcard/bool/when、分页、排序 | -| 5 | aggs/terms/stats/dateHistogram、嵌套聚合 | -| 6 | Bulk update、source(['ids'])、冲突重试 | -| 7 | putMapping、Rebuild(schema 变更) | -| 8 | 事件监听、数据校验 | - -## 不在指南范围内的 - -- Canal/binlog 监听部署(属于基础设施) -- Laravel Queue 配置(属于框架知识) -- ES 集群调优(属于运维范畴) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 400bc2b..f5e9a20 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -57,7 +57,30 @@ parameters: - message: '#^Call to an undefined method Elastic\\Elasticsearch\\ClientInterface\:\:indices\(\)\.$#' identifier: method.notFound - count: 4 + count: 5 + path: src/Index/Rebuild.php + + - + message: '#^Call to an undefined method Elastic\\Elasticsearch\\ClientInterface\:\:exists\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Index/Rebuild.php + + - + message: '#^Call to an undefined method Elastic\\Elasticsearch\\ClientInterface\:\:index\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Index/Rebuild.php + + - + message: '#^Call to an undefined method Elastic\\Elasticsearch\\ClientInterface\:\:delete\(\)\.$#' + identifier: method.notFound + count: 2 + path: src/Index/Rebuild.php + + - + message: '#^If condition is always false\.$#' + identifier: if.alwaysFalse path: src/Index/Rebuild.php - diff --git a/phpunit.xml b/phpunit.xml index 944f10f..4166425 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,15 +1,12 @@ - - - - tests - tests/Index - - - tests/Index - - + + + + tests + tests/Index + + + tests/Index + + diff --git a/src/DSL/Agg.php b/src/DSL/Agg.php index cb7c565..fd27e51 100644 --- a/src/DSL/Agg.php +++ b/src/DSL/Agg.php @@ -1,5 +1,7 @@ */ - protected $subAggs = []; + protected array $_subAggs = []; /** - * Properties for array-based aggregation definitions. - * Supports nested Query, Node, and Agg instances (resolved by resolveProperties). + * Properties for array-based aggregation definitions (raw DSL mode). + * Null when the aggregation is node-based or empty. * * @var array|null */ - protected $_properties; + protected ?array $_properties = null; /** * Static factory — thin proxy over the constructor. @@ -56,7 +54,7 @@ class Agg * @param mixed $agg * @return static */ - public static function create($agg = []) + public static function create($agg = []): static { if ($agg instanceof static) { return $agg; @@ -83,7 +81,7 @@ public static function create($agg = []) * @param Node $node * @return $this */ - protected function node($node) + protected function node(Node $node): static { $this->_node = $node; $this->_properties = null; @@ -96,12 +94,12 @@ protected function node($node) * Similar to Node::field(), the alias wraps the output: * {"alias_name": {"terms": {"field": "status"}}}. * - * @param string $alias + * @param string $value * @return $this */ - public function alias($alias) + public function alias(string $value): static { - $this->_alias = $alias; + $this->_alias = $value; return $this; } @@ -110,7 +108,7 @@ public function alias($alias) * * @return string|null */ - public function getAlias() + public function getAlias(): ?string { return $this->_alias; } @@ -128,7 +126,7 @@ public function getAlias() * @return $this * @throws \BadMethodCallException if called with a string alias and no definition */ - public function aggs($alias, $aggs = null) + public function aggs($alias, $aggs = null): static { if ($aggs === null && !is_string($alias)) { $aggs = $alias; @@ -139,7 +137,7 @@ public function aggs($alias, $aggs = null) if ($alias !== null) { $aggs->alias($alias); } - $this->subAggs[$alias ?? $aggs->getAlias()] = $aggs; + $this->_subAggs[$alias ?? $aggs->getAlias()] = $aggs; return $this; } @@ -148,17 +146,17 @@ public function aggs($alias, $aggs = null) if ($alias !== null) { $childAgg->alias($alias); } - $this->subAggs[$alias] = $childAgg; + $this->_subAggs[$alias] = $childAgg; return $this; } - if ($alias !== null && !isset($this->subAggs[$alias])) { - $this->subAggs[$alias] = new Agg(); - $this->subAggs[$alias]->alias($alias); + if ($alias !== null && !isset($this->_subAggs[$alias])) { + $this->_subAggs[$alias] = new Agg(); + $this->_subAggs[$alias]->alias($alias); } if ($aggs instanceof \Closure) { - $aggs($this->subAggs[$alias]); + $aggs($this->_subAggs[$alias]); return $this; } @@ -177,7 +175,7 @@ public function aggs($alias, $aggs = null) * @param array $properties * @return array */ - protected function resolveProperties(array $properties) + protected function resolveProperties(array $properties): array { foreach ($properties as $key => $property) { if ($property instanceof Query) { @@ -199,7 +197,7 @@ protected function resolveProperties(array $properties) * * @return array */ - public function toArray() + public function toArray(): array { if ($this->_properties !== null) { $resolved = $this->resolveProperties($this->_properties); @@ -215,9 +213,9 @@ public function toArray() $inner[$this->_node->key()] = $this->_node->toArray(); } - if (!empty($this->subAggs)) { + if (!empty($this->_subAggs)) { $inner['aggs'] = []; - foreach ($this->subAggs as $subAgg) { + foreach ($this->_subAggs as $subAgg) { $inner['aggs'] += $subAgg->toArray(); } } @@ -236,9 +234,11 @@ public function toArray() * @param int $depth * @return string */ - public function toJson($flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, $depth = 512) + public function toJson(int $flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, int $depth = 512): string { - return json_encode($this->toArray(), $flags, $depth); + $json = json_encode($this->toArray(), $flags, $depth); + + return $json === false ? '' : $json; } /** @@ -246,7 +246,7 @@ public function toJson($flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, $dep * * @return string */ - public function __toString() + public function __toString(): string { return $this->toJson(); } diff --git a/src/DSL/Aggs/Bucket.php b/src/DSL/Aggs/Bucket.php index e31eb30..7519c2a 100644 --- a/src/DSL/Aggs/Bucket.php +++ b/src/DSL/Aggs/Bucket.php @@ -1,5 +1,7 @@ node(Terms::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Terms::create(is_string($value) ? ['field' => $value] : $value)); } /** * Defines a single bucket that limits documents matching a query. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - $instance = new FilterAgg(); - $instance->setFilter($filter); + $instance = new Filter(); + $instance->filter($value); return $this->node($instance); } /** * Defines multiple buckets from multiple filters, one per filter expression. * - * @param mixed $params + * @param mixed $value * @return static */ - public function filters($params) + public function filters($value): static { - return $this->node(Filters::create($params)); + return $this->node(Filters::create($value)); } /** * Groups documents into buckets based on combinations of filter expressions. * - * @param mixed $params + * @param mixed $value * @return static */ - public function adjacencyMatrix($params) + public function adjacencyMatrix($value): static { - return $this->node(AdjacencyMatrix::create($params)); + return $this->node(AdjacencyMatrix::create($value)); } /** * Automatically determines bucket intervals for date fields based on document count. * - * @param mixed $params + * @param mixed $value * @return static */ - public function autoDateHistogram($params) + public function autoDateHistogram($value): static { - return $this->node(AutoDateHistogram::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(AutoDateHistogram::create(is_string($value) ? ['field' => $value] : $value)); } /** * Extracts categories from text fields by tokenizing and grouping values. * - * @param mixed $params + * @param mixed $value * @return static */ - public function categorizeText($params) + public function categorizeText($value): static { - return $this->node(CategorizeText::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(CategorizeText::create(is_string($value) ? ['field' => $value] : $value)); } /** * Creates composite buckets from multiple source values, supporting pagination. * - * @param mixed $params + * @param mixed $value * @return static */ - public function composite($params) + public function composite($value): static { - return $this->node(Composite::create($params)); + return $this->node(Composite::create($value)); } /** * Groups documents into buckets by date interval (e.g. per day, per month). * - * @param mixed $params + * @param mixed $value * @return static */ - public function dateHistogram($params) + public function dateHistogram($value): static { - return $this->node(DateHistogram::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(DateHistogram::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into buckets by user-defined date ranges. * - * @param mixed $params + * @param mixed $value * @return static */ - public function dateRange($params) + public function dateRange($value): static { - return $this->node(DateRange::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(DateRange::create(is_string($value) ? ['field' => $value] : $value)); } /** * Limits any child aggregations to a diversified sample of top-scoring documents. * - * @param mixed $params + * @param mixed $value * @return static */ - public function diversifiedSampler($params) + public function diversifiedSampler($value): static { - return $this->node(DiversifiedSampler::create($params)); + return $this->node(DiversifiedSampler::create($value)); } /** * Finds frequently co-occurring item sets in array fields. * - * @param mixed $params + * @param mixed $value * @return static */ - public function frequentItemSets($params) + public function frequentItemSets($value): static { - return $this->node(FrequentItemSets::create($params)); + return $this->node(FrequentItemSets::create($value)); } /** * Groups documents into buckets by distance ranges from a geo point. * - * @param mixed $params + * @param mixed $value * @return static */ - public function geoDistance($params) + public function geoDistance($value): static { - return $this->node(GeoDistance::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(GeoDistance::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into grid cells using geohash prefixes. * - * @param mixed $params + * @param mixed $value * @return static */ - public function geoHashGrid($params) + public function geoHashGrid($value): static { - return $this->node(GeoHashGrid::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(GeoHashGrid::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into grid cells using H3 hexagon indexes. * - * @param mixed $params + * @param mixed $value * @return static */ - public function geohexGrid($params) + public function geohexGrid($value): static { - return $this->node(GeohexGrid::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(GeohexGrid::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into grid cells using geotile prefixes. * - * @param mixed $params + * @param mixed $value * @return static */ - public function geotileGrid($params) + public function geotileGrid($value): static { - return $this->node(GeotileGrid::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(GeotileGrid::create(is_string($value) ? ['field' => $value] : $value)); } /** @@ -208,173 +210,173 @@ public function geotileGrid($params) * * @return static */ - public function globalAggregation() + public function global(): static { - return $this->node(new GlobalAgg()); + return $this->node(new Global_()); } /** * Groups documents into buckets by numeric interval. * - * @param mixed $params + * @param mixed $value * @return static */ - public function histogram($params) + public function histogram($value): static { - return $this->node(Histogram::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Histogram::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into buckets by IP address prefix. * - * @param mixed $params + * @param mixed $value * @return static */ - public function ipPrefix($params) + public function ipPrefix($value): static { - return $this->node(IpPrefix::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(IpPrefix::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into buckets by user-defined IP address ranges. * - * @param mixed $params + * @param mixed $value * @return static */ - public function ipRange($params) + public function ipRange($value): static { - return $this->node(IpRange::create($params)); + return $this->node(IpRange::create($value)); } /** * Creates a single bucket for documents missing a field value. * - * @param mixed $params + * @param mixed $value * @return static */ - public function missing($params) + public function missing($value): static { - return $this->node(Missing::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Missing::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into buckets by multiple field term combinations. * - * @param mixed $params + * @param mixed $value * @return static */ - public function multiTerms($params) + public function multiTerms($value): static { - return $this->node(MultiTerms::create($params)); + return $this->node(MultiTerms::create($value)); } /** * Aggregates on nested documents within a parent document. * - * @param mixed $params + * @param mixed $value * @return static */ - public function nested($params) + public function nested($value): static { - return $this->node(Nested::create($params)); + return $this->node(Nested::create($value)); } /** * Aggregates on parent documents from a child document context in a join relation. * - * @param mixed $params + * @param mixed $value * @return static */ - public function parent($params) + public function parent($value): static { - return $this->node(ParentAgg::create($params)); + return $this->node(Parent_::create($value)); } /** * Limits any child aggregations to a random sample of documents. * - * @param mixed $params + * @param mixed $value * @return static */ - public function randomSampler($params) + public function randomSampler($value): static { - return $this->node(RandomSampler::create($params)); + return $this->node(RandomSampler::create($value)); } /** * Groups documents into buckets by user-defined numeric ranges. * - * @param mixed $params + * @param mixed $value * @return static */ - public function range($params) + public function range($value): static { - return $this->node(Range::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Range::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into buckets by rare field values with low document counts. * - * @param mixed $params + * @param mixed $value * @return static */ - public function rareTerms($params) + public function rareTerms($value): static { - return $this->node(RareTerms::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(RareTerms::create(is_string($value) ? ['field' => $value] : $value)); } /** * Aggregates on parent documents from within a nested aggregation context. * - * @param mixed $params + * @param mixed $value * @return static */ - public function reverseNested($params = []) + public function reverseNested($value = []): static { - return $this->node(ReverseNested::create($params)); + return $this->node(ReverseNested::create($value)); } /** * Finds field values that are unusually common in a subset compared to the whole index. * - * @param mixed $params + * @param mixed $value * @return static */ - public function significantTerms($params) + public function significantTerms($value): static { - return $this->node(SignificantTerms::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(SignificantTerms::create(is_string($value) ? ['field' => $value] : $value)); } /** * Finds significant terms from text field content without needing a sub-field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function significantText($params) + public function significantText($value): static { - return $this->node(SignificantText::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(SignificantText::create(is_string($value) ? ['field' => $value] : $value)); } /** * Groups documents into time series buckets for time-series data. * - * @param mixed $params + * @param mixed $value * @return static */ - public function timeSeries($params) + public function timeSeries($value): static { - return $this->node(TimeSeries::create($params)); + return $this->node(TimeSeries::create($value)); } /** * Groups documents into dynamically sized histogram buckets based on data distribution. * - * @param mixed $params + * @param mixed $value * @return static */ - public function variableWidthHistogram($params) + public function variableWidthHistogram($value): static { - return $this->node(VariableWidthHistogram::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(VariableWidthHistogram::create(is_string($value) ? ['field' => $value] : $value)); } } diff --git a/src/DSL/Aggs/Bucket/AdjacencyMatrix.php b/src/DSL/Aggs/Bucket/AdjacencyMatrix.php index efcd155..7bd2425 100644 --- a/src/DSL/Aggs/Bucket/AdjacencyMatrix.php +++ b/src/DSL/Aggs/Bucket/AdjacencyMatrix.php @@ -1,5 +1,7 @@ */ - protected $_filters; + protected string $_key = 'adjacency_matrix'; /** - * Filters used to create buckets. + * Add a named filter used to create buckets. * * @param string $key * @param mixed $query * @return static */ - public function filters($key, $query) + public function filters($key, $query): static { - $this->_filters[$key] = Query::create($query); - return $this->addProperty('filters', $this->_filters); + $this->_properties ??= []; + $this->_properties['filters'][$key] = Query::create($query); + return $this; } /** * Separator used to concatenate filter names. Defaults to &. * - * @param string $separator + * @param string $value * @return static */ - public function separator($separator) + public function separator(string $value): static { - return $this->addProperty('separator', $separator); - } - - /** - * {@inheritdoc} - */ - public function toArray() - { - $properties = $this->_properties; - - if (isset($properties['filters'])) { - foreach ($properties['filters'] as $key => $filter) { - if ($filter instanceof Query) { - $properties['filters'][$key] = $filter->toArray()['query']; - } - } - } - - $properties = $this->resolveProperties($properties); - - if ($this->_isPropertyField) { - return [$this->_field => $properties]; - } - return $properties; + return $this->addProperty('separator', $value); } } diff --git a/src/DSL/Aggs/Bucket/AutoDateHistogram.php b/src/DSL/Aggs/Bucket/AutoDateHistogram.php index f2c1172..518ec28 100644 --- a/src/DSL/Aggs/Bucket/AutoDateHistogram.php +++ b/src/DSL/Aggs/Bucket/AutoDateHistogram.php @@ -1,5 +1,7 @@ addProperty('buckets', $buckets); + return $this->addProperty('buckets', $value); } /** * Date format pattern for bucket keys. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** * Time zone for bucketing. * - * @param string $timeZone + * @param string $value * @return static */ - public function timeZone($timeZone) + public function timeZone(string $value): static { - return $this->addProperty('time_zone', $timeZone); + return $this->addProperty('time_zone', $value); } /** * Minimum interval to use when automatically determining buckets. * - * @param string $minimumInterval + * @param string $value * @return static */ - public function minimumInterval($minimumInterval) + public function minimumInterval(string $value): static { - return $this->addProperty('minimum_interval', $minimumInterval); + return $this->addProperty('minimum_interval', $value); } /** * Value to use for documents missing the field value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Bucket/CategorizeText.php b/src/DSL/Aggs/Bucket/CategorizeText.php index c54f088..91bcdb5 100644 --- a/src/DSL/Aggs/Bucket/CategorizeText.php +++ b/src/DSL/Aggs/Bucket/CategorizeText.php @@ -1,5 +1,7 @@ addProperty('categorization_analyzer', $categorizationAnalyzer); + return $this->addProperty('categorization_analyzer', $value); } /** * Filters applied to each token before categorization. * - * @param array $categorizationFilters + * @param array $value * @return static */ - public function categorizationFilters($categorizationFilters) + public function categorizationFilters(array $value): static { - return $this->addProperty('categorization_filters', $categorizationFilters); + return $this->addProperty('categorization_filters', $value); } /** * Maximum number of matched tokens to consider. * - * @param int $maxMatchedTokens + * @param int $value * @return static */ - public function maxMatchedTokens($maxMatchedTokens) + public function maxMatchedTokens(int $value): static { - return $this->addProperty('max_matched_tokens', $maxMatchedTokens); + return $this->addProperty('max_matched_tokens', $value); } /** * Maximum number of unique tokens to consider. * - * @param int $maxUniqueTokens + * @param int $value * @return static */ - public function maxUniqueTokens($maxUniqueTokens) + public function maxUniqueTokens(int $value): static { - return $this->addProperty('max_unique_tokens', $maxUniqueTokens); + return $this->addProperty('max_unique_tokens', $value); } /** * Minimum document count per bucket. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Minimum document count per shard. * - * @param int $shardMinDocCount + * @param int $value * @return static */ - public function shardMinDocCount($shardMinDocCount) + public function shardMinDocCount(int $value): static { - return $this->addProperty('shard_min_doc_count', $shardMinDocCount); + return $this->addProperty('shard_min_doc_count', $value); } /** * Number of categories to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** * Similarity threshold for grouping categories. * - * @param float $similarityThreshold + * @param float $value * @return static */ - public function similarityThreshold($similarityThreshold) + public function similarityThreshold(float $value): static { - return $this->addProperty('similarity_threshold', $similarityThreshold); + return $this->addProperty('similarity_threshold', $value); } /** * Maximum number of categories to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } } diff --git a/src/DSL/Aggs/Bucket/Composite.php b/src/DSL/Aggs/Bucket/Composite.php index 681ef95..8614888 100644 --- a/src/DSL/Aggs/Bucket/Composite.php +++ b/src/DSL/Aggs/Bucket/Composite.php @@ -1,5 +1,7 @@ addProperty('sources', $sources); + return $this->addProperty('sources', $value); } /** * Cursor value to resume pagination after a previous composite response. * - * @param mixed $after + * @param mixed $value * @return static */ - public function after($after) + public function after($value): static { - return $this->addProperty('after', $after); + return $this->addProperty('after', $value); } /** * Sort order for composite buckets. * - * @param mixed $order + * @param mixed $value * @return static */ - public function order($order) + public function order($value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** * Maximum number of composite buckets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) - { - return $this->addProperty('size', $size); - } - - /** - * {@inheritdoc} - * @SuppressWarnings(PHPMD.IfStatementAssignment) - */ - public function toArray() + public function size(int $value): static { - $properties = $this->_properties; - - if (isset($properties['sources'])) { - foreach ($properties['sources'] as $key => $source) { - if (($item = current($source)) instanceof Node) { - $properties['sources'][$key] = $item->toArray(); - } - } - } - - $properties = $this->resolveProperties($properties); - - if ($this->_isPropertyField) { - return [$this->_field => $properties]; - } - return $properties; + return $this->addProperty('size', $value); } } diff --git a/src/DSL/Aggs/Bucket/DateHistogram.php b/src/DSL/Aggs/Bucket/DateHistogram.php index 2e1d01b..82110b2 100644 --- a/src/DSL/Aggs/Bucket/DateHistogram.php +++ b/src/DSL/Aggs/Bucket/DateHistogram.php @@ -1,5 +1,7 @@ addProperty('calendar_interval', $calendarInterval); + return $this->addProperty('calendar_interval', $value); } /** * Interval for bucketing. Deprecated in favor of calendar_interval or fixed_interval. * - * @param string $interval + * @param string $value * @return static */ - public function interval($interval) + public function interval(string $value): static { - return $this->addProperty('interval', $interval); + return $this->addProperty('interval', $value); } /** * Fixed-unit interval for bucketing (e.g. 30d, 12h). * - * @param string $fixedInterval + * @param string $value * @return static */ - public function fixedInterval($fixedInterval) + public function fixedInterval(string $value): static { - return $this->addProperty('fixed_interval', $fixedInterval); + return $this->addProperty('fixed_interval', $value); } /** * Date format pattern for bucket keys. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** * Time zone for bucketing. * - * @param string $timeZone + * @param string $value * @return static */ - public function timeZone($timeZone) + public function timeZone(string $value): static { - return $this->addProperty('time_zone', $timeZone); + return $this->addProperty('time_zone', $value); } /** * Minimum number of documents in a bucket to be returned. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Extends the bucket range beyond the data bounds. * - * @param mixed $extendedBounds + * @param mixed $value * @return static */ - public function extendedBounds($extendedBounds) + public function extendedBounds($value): static { - return $this->addProperty('extended_bounds', $extendedBounds); + return $this->addProperty('extended_bounds', $value); } /** * Limits the bucket range to a bounded range. * - * @param mixed $hardBounds + * @param mixed $value * @return static */ - public function hardBounds($hardBounds) + public function hardBounds($value): static { - return $this->addProperty('hard_bounds', $hardBounds); + return $this->addProperty('hard_bounds', $value); } /** * Sort order for buckets. * - * @param mixed $order + * @param mixed $value * @return static */ - public function order($order) + public function order($value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** * Whether to return bucket keys as strings. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } /** * Value to use for documents missing the field value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** * Offset for each bucket start time. * - * @param string $offset + * @param string $value * @return static */ - public function offset($offset) + public function offset(string $value): static { - return $this->addProperty('offset', $offset); + return $this->addProperty('offset', $value); } } diff --git a/src/DSL/Aggs/Bucket/DateRange.php b/src/DSL/Aggs/Bucket/DateRange.php index e7a6638..e62b25d 100644 --- a/src/DSL/Aggs/Bucket/DateRange.php +++ b/src/DSL/Aggs/Bucket/DateRange.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'date_range'; /** * Array of range definitions for bucketing. * - * @param array $ranges + * @param array $value * @return static */ - public function ranges($ranges) + public function ranges(array $value): static { - return $this->addProperty('ranges', $ranges); + return $this->addProperty('ranges', $value); } /** * Whether to return range buckets as a hash keyed by range key. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } /** * Date format pattern for range keys. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** * Value to use for documents missing the field value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** * Time zone for date calculations. * - * @param string $timeZone + * @param string $value * @return static */ - public function timeZone($timeZone) + public function timeZone(string $value): static { - return $this->addProperty('time_zone', $timeZone); + return $this->addProperty('time_zone', $value); } } diff --git a/src/DSL/Aggs/Bucket/DiversifiedSampler.php b/src/DSL/Aggs/Bucket/DiversifiedSampler.php index 781676d..938127a 100644 --- a/src/DSL/Aggs/Bucket/DiversifiedSampler.php +++ b/src/DSL/Aggs/Bucket/DiversifiedSampler.php @@ -1,5 +1,7 @@ addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** * Maximum number of documents per unique value. * - * @param int $maxDocsPerValue + * @param int $value * @return static */ - public function maxDocsPerValue($maxDocsPerValue) + public function maxDocsPerValue(int $value): static { - return $this->addProperty('max_docs_per_value', $maxDocsPerValue); + return $this->addProperty('max_docs_per_value', $value); } /** * Execution hint for the aggregation. * - * @param string $executionHint + * @param string $value * @return static */ - public function executionHint($executionHint) + public function executionHint(string $value): static { - return $this->addProperty('execution_hint', $executionHint); + return $this->addProperty('execution_hint', $value); } } diff --git a/src/DSL/Aggs/Bucket/FilterAgg.php b/src/DSL/Aggs/Bucket/Filter.php similarity index 54% rename from src/DSL/Aggs/Bucket/FilterAgg.php rename to src/DSL/Aggs/Bucket/Filter.php index f2c8f54..0adf209 100644 --- a/src/DSL/Aggs/Bucket/FilterAgg.php +++ b/src/DSL/Aggs/Bucket/Filter.php @@ -1,5 +1,7 @@ _properties = $filter; + $this->_filter = $value; return $this; } @@ -31,6 +36,6 @@ public function setFilter($filter) */ public function toArray() { - return Query::create($this->_properties)->toArray()['query']; + return Query::create($this->_filter)->toArray()['query']; } } diff --git a/src/DSL/Aggs/Bucket/Filters.php b/src/DSL/Aggs/Bucket/Filters.php index e6502f8..0e42e6e 100644 --- a/src/DSL/Aggs/Bucket/Filters.php +++ b/src/DSL/Aggs/Bucket/Filters.php @@ -1,5 +1,7 @@ addProperty('filters', $filters); + return $this->addProperty('filters', $value); } /** * Key for the bucket that holds documents not matching any filter. * - * @param string $otherBucketKey + * @param string $value * @return static */ - public function otherBucketKey($otherBucketKey) + public function otherBucketKey(string $value): static { - return $this->addProperty('other_bucket_key', $otherBucketKey); + return $this->addProperty('other_bucket_key', $value); } /** * Whether to return buckets as a hash keyed by filter name. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } } diff --git a/src/DSL/Aggs/Bucket/FrequentItemSets.php b/src/DSL/Aggs/Bucket/FrequentItemSets.php index a5c944e..7a66431 100644 --- a/src/DSL/Aggs/Bucket/FrequentItemSets.php +++ b/src/DSL/Aggs/Bucket/FrequentItemSets.php @@ -1,5 +1,7 @@ addProperty('minimum_set_size', $minimumSetSize); + return $this->addProperty('minimum_set_size', $value); } /** * Fields to analyze for frequent item sets. * - * @param array $fields + * @param array $value * @return static */ - public function fields($fields) + public function fields(array $value): static { - return $this->addProperty('fields', $fields); + return $this->addProperty('fields', $value); } /** * Maximum number of item sets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Query to filter documents before analysis. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', $filter); + return $this->addProperty('filter', $value); } } diff --git a/src/DSL/Aggs/Bucket/GeoDistance.php b/src/DSL/Aggs/Bucket/GeoDistance.php index 3e6dcdd..77411db 100644 --- a/src/DSL/Aggs/Bucket/GeoDistance.php +++ b/src/DSL/Aggs/Bucket/GeoDistance.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'geo_distance'; /** * The central geo point from which distances are measured. * - * @param mixed $origin + * @param mixed $value * @return static */ - public function origin($origin) + public function origin($value): static { - return $this->addProperty('origin', $origin); + return $this->addProperty('origin', $value); } /** * Distance unit (e.g. km, mi, m). * - * @param string $unit + * @param string $value * @return static */ - public function unit($unit) + public function unit(string $value): static { - return $this->addProperty('unit', $unit); + return $this->addProperty('unit', $value); } /** * How to compute the distance (arc or plane). * - * @param string $distanceType + * @param string $value * @return static */ - public function distanceType($distanceType) + public function distanceType(string $value): static { - return $this->addProperty('distance_type', $distanceType); + return $this->addProperty('distance_type', $value); } /** * Array of distance range definitions for bucketing. * - * @param array $ranges + * @param array $value * @return static */ - public function ranges($ranges) + public function ranges(array $value): static { - return $this->addProperty('ranges', $ranges); + return $this->addProperty('ranges', $value); } /** * Whether to return range buckets as a hash keyed by range key. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } } diff --git a/src/DSL/Aggs/Bucket/GeoHashGrid.php b/src/DSL/Aggs/Bucket/GeoHashGrid.php index 6933f99..9e46e3b 100644 --- a/src/DSL/Aggs/Bucket/GeoHashGrid.php +++ b/src/DSL/Aggs/Bucket/GeoHashGrid.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'geohash_grid'; /** * Geohash precision (length) for grid cells. * - * @param int $precision + * @param int $value * @return static */ - public function precision($precision) + public function precision(int $value): static { - return $this->addProperty('precision', $precision); + return $this->addProperty('precision', $value); } /** * Maximum number of geohash buckets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Number of geohash buckets to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } } diff --git a/src/DSL/Aggs/Bucket/GeohexGrid.php b/src/DSL/Aggs/Bucket/GeohexGrid.php index fa8b446..5f93015 100644 --- a/src/DSL/Aggs/Bucket/GeohexGrid.php +++ b/src/DSL/Aggs/Bucket/GeohexGrid.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'geohex_grid'; /** * H3 resolution for grid cells. * - * @param int $precision + * @param int $value * @return static */ - public function precision($precision) + public function precision(int $value): static { - return $this->addProperty('precision', $precision); + return $this->addProperty('precision', $value); } /** * Maximum number of hex buckets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Number of hex buckets to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } } diff --git a/src/DSL/Aggs/Bucket/GeotileGrid.php b/src/DSL/Aggs/Bucket/GeotileGrid.php index 6f60cec..aa61d5a 100644 --- a/src/DSL/Aggs/Bucket/GeotileGrid.php +++ b/src/DSL/Aggs/Bucket/GeotileGrid.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'geotile_grid'; /** * Zoom level (precision) for geotile grid cells. * - * @param int $precision + * @param int $value * @return static */ - public function precision($precision) + public function precision(int $value): static { - return $this->addProperty('precision', $precision); + return $this->addProperty('precision', $value); } /** * Maximum number of geotile buckets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Number of geotile buckets to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } } diff --git a/src/DSL/Aggs/Bucket/GlobalAgg.php b/src/DSL/Aggs/Bucket/Global_.php similarity index 79% rename from src/DSL/Aggs/Bucket/GlobalAgg.php rename to src/DSL/Aggs/Bucket/Global_.php index 9fd6f86..8efd193 100644 --- a/src/DSL/Aggs/Bucket/GlobalAgg.php +++ b/src/DSL/Aggs/Bucket/Global_.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'histogram'; /** * Interval size for each bucket. * - * @param float $interval + * @param float $value * @return static */ - public function interval($interval) + public function interval(float $value): static { - return $this->addProperty('interval', $interval); + return $this->addProperty('interval', $value); } /** * Minimum number of documents in a bucket to be returned. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Extends the bucket range beyond the data bounds. * - * @param mixed $bounds + * @param mixed $value * @return static */ - public function extendedBounds($bounds) + public function extendedBounds($value): static { - return $this->addProperty('extended_bounds', $bounds); + return $this->addProperty('extended_bounds', $value); } /** * Sort order for buckets. * - * @param mixed $order + * @param mixed $value * @return static */ - public function order($order) + public function order($value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** * Whether to return bucket keys as strings. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } /** * Value to use for documents missing the field value. * - * @param float $missing + * @param float $value * @return static */ - public function missing($missing) + public function missing(float $value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** * Format pattern for bucket key values. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** * Script to compute the bucket value. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } /** * Offset for bucket starting values. * - * @param float $offset + * @param float $value * @return static */ - public function offset($offset) + public function offset(float $value): static { - return $this->addProperty('offset', $offset); + return $this->addProperty('offset', $value); } /** * Limits the bucket range to a bounded range. * - * @param mixed $hardBounds + * @param mixed $value * @return static */ - public function hardBounds($hardBounds) + public function hardBounds($value): static { - return $this->addProperty('hard_bounds', $hardBounds); + return $this->addProperty('hard_bounds', $value); } } diff --git a/src/DSL/Aggs/Bucket/IpPrefix.php b/src/DSL/Aggs/Bucket/IpPrefix.php index 8d90deb..433ad0c 100644 --- a/src/DSL/Aggs/Bucket/IpPrefix.php +++ b/src/DSL/Aggs/Bucket/IpPrefix.php @@ -1,39 +1,32 @@ addProperty('field', $field); - } + protected string $_key = 'ip_prefix'; /** * Length of the network prefix. * - * @param int $length + * @param int $value * @return static */ - public function prefixLength($length) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $length); + return $this->addProperty('prefix_length', $value); } /** - * @param int $length + * @param int $value * @return static */ - public function minPrefixLength($length) + public function minPrefixLength(int $value): static { - return $this->addProperty('min_prefix_length', $length); + return $this->addProperty('min_prefix_length', $value); } } diff --git a/src/DSL/Aggs/Bucket/IpRange.php b/src/DSL/Aggs/Bucket/IpRange.php index 46661ec..51f1cb3 100644 --- a/src/DSL/Aggs/Bucket/IpRange.php +++ b/src/DSL/Aggs/Bucket/IpRange.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'ip_range'; /** * Array of IP range definitions for bucketing. * - * @param array $ranges + * @param array $value * @return static */ - public function ranges($ranges) + public function ranges(array $value): static { - return $this->addProperty('ranges', $ranges); + return $this->addProperty('ranges', $value); } /** * Whether to return range buckets as a hash keyed by range key. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } /** * Value to use for documents missing the field value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Bucket/Missing.php b/src/DSL/Aggs/Bucket/Missing.php index bea6610..c445f03 100644 --- a/src/DSL/Aggs/Bucket/Missing.php +++ b/src/DSL/Aggs/Bucket/Missing.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'missing'; /** * Value to treat as missing for the field. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Bucket/MultiTerms.php b/src/DSL/Aggs/Bucket/MultiTerms.php index 4d3859d..98620b4 100644 --- a/src/DSL/Aggs/Bucket/MultiTerms.php +++ b/src/DSL/Aggs/Bucket/MultiTerms.php @@ -1,73 +1,75 @@ addProperty('terms', $terms, true); + return $this->addProperty('terms', $value, true); } /** - * @param mixed $order + * @param mixed $value * @return static */ - public function order($order) + public function order($value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** - * @param int $shardMinDocCount + * @param int $value * @return static */ - public function shardMinDocCount($shardMinDocCount) + public function shardMinDocCount(int $value): static { - return $this->addProperty('shard_min_doc_count', $shardMinDocCount); + return $this->addProperty('shard_min_doc_count', $value); } /** - * @param string $collectMode + * @param string $value * @return static */ - public function collectMode($collectMode) + public function collectMode(string $value): static { - return $this->addProperty('collect_mode', $collectMode); + return $this->addProperty('collect_mode', $value); } } diff --git a/src/DSL/Aggs/Bucket/Nested.php b/src/DSL/Aggs/Bucket/Nested.php index d30200b..5f185fc 100644 --- a/src/DSL/Aggs/Bucket/Nested.php +++ b/src/DSL/Aggs/Bucket/Nested.php @@ -1,5 +1,7 @@ addProperty('path', $path); + return $this->addProperty('path', $value); } /** * Whether to return an empty bucket instead of an error for unmapped nested types. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Aggs/Bucket/ParentAgg.php b/src/DSL/Aggs/Bucket/Parent_.php similarity index 57% rename from src/DSL/Aggs/Bucket/ParentAgg.php rename to src/DSL/Aggs/Bucket/Parent_.php index b60e61b..82b4154 100644 --- a/src/DSL/Aggs/Bucket/ParentAgg.php +++ b/src/DSL/Aggs/Bucket/Parent_.php @@ -1,5 +1,7 @@ addProperty('type', $type); + return $this->addProperty('type', $value); } } diff --git a/src/DSL/Aggs/Bucket/RandomSampler.php b/src/DSL/Aggs/Bucket/RandomSampler.php index de1b946..3e5217d 100644 --- a/src/DSL/Aggs/Bucket/RandomSampler.php +++ b/src/DSL/Aggs/Bucket/RandomSampler.php @@ -1,5 +1,7 @@ addProperty('probability', $probability); + return $this->addProperty('probability', $value); } /** * Seed for the random number generator to produce repeatable samples. * - * @param int $seed - * @return static - */ - public function seed($seed) - { - return $this->addProperty('seed', $seed); - } - - /** - * Field used to maintain consistent random ordering across shards. - * - * @param string $field + * @param int $value * @return static */ - public function field($field) + public function seed(int $value): static { - return $this->addProperty('field', $field); + return $this->addProperty('seed', $value); } } diff --git a/src/DSL/Aggs/Bucket/Range.php b/src/DSL/Aggs/Bucket/Range.php index b488c66..e87eaae 100644 --- a/src/DSL/Aggs/Bucket/Range.php +++ b/src/DSL/Aggs/Bucket/Range.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'range'; /** * Array of range definitions for bucketing. * - * @param array $ranges + * @param array $value * @return static */ - public function ranges($ranges) + public function ranges(array $value): static { - return $this->addProperty('ranges', $ranges); + return $this->addProperty('ranges', $value); } /** * Whether to return range buckets as a hash keyed by range key. * - * @param bool $keyed + * @param bool $value * @return static */ - public function keyed($keyed) + public function keyed(bool $value): static { - return $this->addProperty('keyed', $keyed); + return $this->addProperty('keyed', $value); } /** * Script to compute the bucket value. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } /** * Value to use for documents missing the field value. * - * @param float $missing + * @param float $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Bucket/RareTerms.php b/src/DSL/Aggs/Bucket/RareTerms.php index 55d678e..ad0ae34 100644 --- a/src/DSL/Aggs/Bucket/RareTerms.php +++ b/src/DSL/Aggs/Bucket/RareTerms.php @@ -1,73 +1,66 @@ addProperty('field', $field); - } + protected string $_key = 'rare_terms'; /** - * @param int $maxDocCount + * @param int $value * @return static */ - public function maxDocCount($maxDocCount) + public function maxDocCount(int $value): static { - return $this->addProperty('max_doc_count', $maxDocCount); + return $this->addProperty('max_doc_count', $value); } /** - * @param mixed $precision + * @param mixed $value * @return static */ - public function precision($precision) + public function precision($value): static { - return $this->addProperty('precision', $precision); + return $this->addProperty('precision', $value); } /** - * @param mixed $include + * @param mixed $value * @return static */ - public function include($include) + public function include($value): static { - return $this->addProperty('include', $include); + return $this->addProperty('include', $value); } /** - * @param mixed $exclude + * @param mixed $value * @return static */ - public function exclude($exclude) + public function exclude($value): static { - return $this->addProperty('exclude', $exclude); + return $this->addProperty('exclude', $value); } /** - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } } diff --git a/src/DSL/Aggs/Bucket/ReverseNested.php b/src/DSL/Aggs/Bucket/ReverseNested.php index ca434eb..39c8d64 100644 --- a/src/DSL/Aggs/Bucket/ReverseNested.php +++ b/src/DSL/Aggs/Bucket/ReverseNested.php @@ -1,5 +1,7 @@ addProperty('path', $path); + return $this->addProperty('path', $value); } } diff --git a/src/DSL/Aggs/Bucket/SignificantTerms.php b/src/DSL/Aggs/Bucket/SignificantTerms.php index 54fe10c..17fe658 100644 --- a/src/DSL/Aggs/Bucket/SignificantTerms.php +++ b/src/DSL/Aggs/Bucket/SignificantTerms.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'significant_terms'; /** * Maximum number of significant terms to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Number of candidate terms to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** * Minimum document count for a term to be returned. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Minimum document count for a term to be considered on each shard. * - * @param int $shardMinDocCount + * @param int $value * @return static */ - public function shardMinDocCount($shardMinDocCount) + public function shardMinDocCount(int $value): static { - return $this->addProperty('shard_min_doc_count', $shardMinDocCount); + return $this->addProperty('shard_min_doc_count', $value); } /** * Terms to include in the aggregation. * - * @param mixed $include + * @param mixed $value * @return static */ - public function include($include) + public function include($value): static { - return $this->addProperty('include', $include); + return $this->addProperty('include', $value); } /** * Terms to exclude from the aggregation. * - * @param mixed $exclude + * @param mixed $value * @return static */ - public function exclude($exclude) + public function exclude($value): static { - return $this->addProperty('exclude', $exclude); + return $this->addProperty('exclude', $value); } /** * Query to filter the background document set for significance calculation. * - * @param mixed $backgroundFilter + * @param mixed $value * @return static */ - public function backgroundFilter($backgroundFilter) + public function backgroundFilter($value): static { - return $this->addProperty('background_filter', $backgroundFilter); + return $this->addProperty('background_filter', $value); } /** * Execution hint for the aggregation mechanism. * - * @param string $executionHint + * @param string $value * @return static */ - public function executionHint($executionHint) + public function executionHint(string $value): static { - return $this->addProperty('execution_hint', $executionHint); + return $this->addProperty('execution_hint', $value); } } diff --git a/src/DSL/Aggs/Bucket/SignificantText.php b/src/DSL/Aggs/Bucket/SignificantText.php index d07832b..cd78ec1 100644 --- a/src/DSL/Aggs/Bucket/SignificantText.php +++ b/src/DSL/Aggs/Bucket/SignificantText.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'significant_text'; /** * Maximum number of significant terms to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Number of candidate terms to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** * Minimum document count for a term to be returned. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Minimum document count for a term to be considered on each shard. * - * @param int $shardMinDocCount + * @param int $value * @return static */ - public function shardMinDocCount($shardMinDocCount) + public function shardMinDocCount(int $value): static { - return $this->addProperty('shard_min_doc_count', $shardMinDocCount); + return $this->addProperty('shard_min_doc_count', $value); } /** * Terms to include in the aggregation. * - * @param mixed $include + * @param mixed $value * @return static */ - public function include($include) + public function include($value): static { - return $this->addProperty('include', $include); + return $this->addProperty('include', $value); } /** * Terms to exclude from the aggregation. * - * @param mixed $exclude + * @param mixed $value * @return static */ - public function exclude($exclude) + public function exclude($value): static { - return $this->addProperty('exclude', $exclude); + return $this->addProperty('exclude', $value); } /** * Query to filter the background document set for significance calculation. * - * @param mixed $backgroundFilter + * @param mixed $value * @return static */ - public function backgroundFilter($backgroundFilter) + public function backgroundFilter($value): static { - return $this->addProperty('background_filter', $backgroundFilter); + return $this->addProperty('background_filter', $value); } /** * Whether to filter duplicate text before analysis. * - * @param bool $filter + * @param bool $value * @return static */ - public function filterDuplicateText($filter) + public function filterDuplicateText(bool $value): static { - return $this->addProperty('filter_duplicate_text', $filter); + return $this->addProperty('filter_duplicate_text', $value); } } diff --git a/src/DSL/Aggs/Bucket/Terms.php b/src/DSL/Aggs/Bucket/Terms.php index ad6531d..d7bf761 100644 --- a/src/DSL/Aggs/Bucket/Terms.php +++ b/src/DSL/Aggs/Bucket/Terms.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'terms'; /** * Maximum number of term buckets to return. * - * @param int $size + * @param int $value * @return static */ - public function size($size) + public function size(int $value): static { - return $this->addProperty('size', $size); + return $this->addProperty('size', $value); } /** * Sort order for term buckets. * - * @param mixed $order + * @param mixed $value * @return static */ - public function order($order) + public function order($value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** * Minimum document count for a term bucket to be returned. * - * @param int $minDocCount + * @param int $value * @return static */ - public function minDocCount($minDocCount) + public function minDocCount(int $value): static { - return $this->addProperty('min_doc_count', $minDocCount); + return $this->addProperty('min_doc_count', $value); } /** * Number of term buckets to return from each shard. * - * @param int $shardSize + * @param int $value * @return static */ - public function shardSize($shardSize) + public function shardSize(int $value): static { - return $this->addProperty('shard_size', $shardSize); + return $this->addProperty('shard_size', $value); } /** * Whether to show document count error for each term. * - * @param bool $show + * @param bool $value * @return static */ - public function showTermDocCountError($show) + public function showTermDocCountError(bool $value): static { - return $this->addProperty('show_term_doc_count_error', $show); + return $this->addProperty('show_term_doc_count_error', $value); } /** * Minimum document count for a term to be considered on each shard. * - * @param int $shardMinDocCount + * @param int $value * @return static */ - public function shardMinDocCount($shardMinDocCount) + public function shardMinDocCount(int $value): static { - return $this->addProperty('shard_min_doc_count', $shardMinDocCount); + return $this->addProperty('shard_min_doc_count', $value); } /** * Value to use for documents missing the field value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** * Collection mode for the aggregation (breadth_first or depth_first). * - * @param string $collectMode + * @param string $value * @return static */ - public function collectMode($collectMode) + public function collectMode(string $value): static { - return $this->addProperty('collect_mode', $collectMode); + return $this->addProperty('collect_mode', $value); } /** * Terms to include in the aggregation. * - * @param mixed $include + * @param mixed $value * @return static */ - public function include($include) + public function include($value): static { - return $this->addProperty('include', $include); + return $this->addProperty('include', $value); } /** * Terms to exclude from the aggregation. * - * @param mixed $exclude + * @param mixed $value * @return static */ - public function exclude($exclude) + public function exclude($value): static { - return $this->addProperty('exclude', $exclude); + return $this->addProperty('exclude', $value); } /** * Execution hint for the aggregation mechanism. * - * @param string $executionHint + * @param string $value * @return static */ - public function executionHint($executionHint) + public function executionHint(string $value): static { - return $this->addProperty('execution_hint', $executionHint); + return $this->addProperty('execution_hint', $value); } } diff --git a/src/DSL/Aggs/Bucket/TimeSeries.php b/src/DSL/Aggs/Bucket/TimeSeries.php index 2497ae8..0d0b144 100644 --- a/src/DSL/Aggs/Bucket/TimeSeries.php +++ b/src/DSL/Aggs/Bucket/TimeSeries.php @@ -1,46 +1,39 @@ addProperty('field', $field); - } + protected string $_key = 'time_series'; /** - * @param string $calendarInterval + * @param string $value * @return static */ - public function calendarInterval($calendarInterval) + public function calendarInterval(string $value): static { - return $this->addProperty('calendar_interval', $calendarInterval); + return $this->addProperty('calendar_interval', $value); } /** - * @param string $fixedInterval + * @param string $value * @return static */ - public function fixedInterval($fixedInterval) + public function fixedInterval(string $value): static { - return $this->addProperty('fixed_interval', $fixedInterval); + return $this->addProperty('fixed_interval', $value); } /** - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Bucket/VariableWidthHistogram.php b/src/DSL/Aggs/Bucket/VariableWidthHistogram.php index 1f4d3ed..df6091e 100644 --- a/src/DSL/Aggs/Bucket/VariableWidthHistogram.php +++ b/src/DSL/Aggs/Bucket/VariableWidthHistogram.php @@ -1,37 +1,30 @@ addProperty('field', $field); - } + protected string $_key = 'variable_width_histogram'; /** - * @param int $buckets + * @param int $value * @return static */ - public function buckets($buckets) + public function buckets(int $value): static { - return $this->addProperty('buckets', $buckets); + return $this->addProperty('buckets', $value); } /** - * @param int $shardBuckets + * @param int $value * @return static */ - public function shardBuckets($shardBuckets) + public function shardBuckets(int $value): static { - return $this->addProperty('shard_buckets', $shardBuckets); + return $this->addProperty('shard_buckets', $value); } } diff --git a/src/DSL/Aggs/Metric.php b/src/DSL/Aggs/Metric.php index d66e88a..30a91ff 100644 --- a/src/DSL/Aggs/Metric.php +++ b/src/DSL/Aggs/Metric.php @@ -1,5 +1,7 @@ node(Avg::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Avg::create(is_string($value) ? ['field' => $value] : $value)); } /** * Computes the sum of numeric values from a field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function sum($params) + public function sum($value): static { - return $this->node(Sum::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Sum::create(is_string($value) ? ['field' => $value] : $value)); } /** * Computes the minimum value from a field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function min($params) + public function min($value): static { - return $this->node(Min::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Min::create(is_string($value) ? ['field' => $value] : $value)); } /** * Computes the maximum value from a field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function max($params) + public function max($value): static { - return $this->node(Max::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Max::create(is_string($value) ? ['field' => $value] : $value)); } /** * Counts the number of distinct values in a field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function cardinality($params) + public function cardinality($value): static { - return $this->node(Cardinality::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Cardinality::create(is_string($value) ? ['field' => $value] : $value)); } /** * Counts the number of values in a field, including duplicates. * - * @param mixed $params + * @param mixed $value * @return static */ - public function valueCount($params) + public function valueCount($value): static { - return $this->node(ValueCount::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(ValueCount::create(is_string($value) ? ['field' => $value] : $value)); } /** * Computes count, min, max, avg, and sum stats from a field in one request. * - * @param mixed $params + * @param mixed $value * @return static */ - public function stats($params) + public function stats($value): static { - return $this->node(Stats::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(Stats::create(is_string($value) ? ['field' => $value] : $value)); } /** * Computes extended statistics (stats plus stddev, variance, std error) from a field. * - * @param mixed $params + * @param mixed $value * @return static */ - public function extendedStats($params) + public function extendedStats($value): static { - return $this->node(ExtendedStats::create(is_string($params) ? ['field' => $params] : $params)); + return $this->node(ExtendedStats::create(is_string($value) ? ['field' => $value] : $value)); } } diff --git a/src/DSL/Aggs/Metric/Avg.php b/src/DSL/Aggs/Metric/Avg.php index 51585ea..9e1ae08 100644 --- a/src/DSL/Aggs/Metric/Avg.php +++ b/src/DSL/Aggs/Metric/Avg.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'avg'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/Cardinality.php b/src/DSL/Aggs/Metric/Cardinality.php index 9f45379..46d5337 100644 --- a/src/DSL/Aggs/Metric/Cardinality.php +++ b/src/DSL/Aggs/Metric/Cardinality.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'cardinality'; /** * Controls the precision of the count. * - * @param int $threshold + * @param int $value * @return static */ - public function precisionThreshold($threshold) + public function precisionThreshold(int $value): static { - return $this->addProperty('precision_threshold', $threshold); + return $this->addProperty('precision_threshold', $value); } /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/ExtendedStats.php b/src/DSL/Aggs/Metric/ExtendedStats.php index 616fd76..57fb326 100644 --- a/src/DSL/Aggs/Metric/ExtendedStats.php +++ b/src/DSL/Aggs/Metric/ExtendedStats.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'extended_stats'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } /** * Number of standard deviations above/below the mean to display. * - * @param int $sigma + * @param int $value * @return static */ - public function sigma($sigma) + public function sigma(int $value): static { - return $this->addProperty('sigma', $sigma); + return $this->addProperty('sigma', $value); } } diff --git a/src/DSL/Aggs/Metric/Max.php b/src/DSL/Aggs/Metric/Max.php index cb71a35..4393603 100644 --- a/src/DSL/Aggs/Metric/Max.php +++ b/src/DSL/Aggs/Metric/Max.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'max'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/Min.php b/src/DSL/Aggs/Metric/Min.php index cb9fd7c..5f3121d 100644 --- a/src/DSL/Aggs/Metric/Min.php +++ b/src/DSL/Aggs/Metric/Min.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'min'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/Stats.php b/src/DSL/Aggs/Metric/Stats.php index 9b7ac44..3467257 100644 --- a/src/DSL/Aggs/Metric/Stats.php +++ b/src/DSL/Aggs/Metric/Stats.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'stats'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/Sum.php b/src/DSL/Aggs/Metric/Sum.php index f86dfbc..063c905 100644 --- a/src/DSL/Aggs/Metric/Sum.php +++ b/src/DSL/Aggs/Metric/Sum.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'sum'; /** - * (Optional) The value to use when the field is missing. + * The value to use when the field is missing. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Metric/ValueCount.php b/src/DSL/Aggs/Metric/ValueCount.php index bd7ef87..055fa7e 100644 --- a/src/DSL/Aggs/Metric/ValueCount.php +++ b/src/DSL/Aggs/Metric/ValueCount.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'value_count'; /** - * (Optional) The script to use for the aggregation. + * The script to use for the aggregation. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } } diff --git a/src/DSL/Aggs/Pipeline.php b/src/DSL/Aggs/Pipeline.php index 48b1461..34d7a74 100644 --- a/src/DSL/Aggs/Pipeline.php +++ b/src/DSL/Aggs/Pipeline.php @@ -1,5 +1,7 @@ node(AvgBucket::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(AvgBucket::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Computes the sum of a metric across sibling aggregation buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function sumBucket($params) + public function sumBucket($value): static { - return $this->node(SumBucket::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(SumBucket::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Finds the bucket with the maximum value of a metric across sibling buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function maxBucket($params) + public function maxBucket($value): static { - return $this->node(MaxBucket::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(MaxBucket::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Finds the bucket with the minimum value of a metric across sibling buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function minBucket($params) + public function minBucket($value): static { - return $this->node(MinBucket::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(MinBucket::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Computes count, min, max, avg, and sum stats across sibling aggregation buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function statsBucket($params) + public function statsBucket($value): static { - return $this->node(StatsBucket::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(StatsBucket::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Computes a cumulative running sum of a metric across parent histogram buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function cumulativeSum($params) + public function cumulativeSum($value): static { - return $this->node(CumulativeSum::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(CumulativeSum::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Computes the derivative of a metric between consecutive parent histogram buckets. * - * @param mixed $params + * @param mixed $value * @return static */ - public function derivative($params) + public function derivative($value): static { - return $this->node(Derivative::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(Derivative::create(is_string($value) ? ['buckets_path' => $value] : $value)); } /** * Runs a custom script to compute values from multiple bucket metrics. * - * @param mixed $params + * @param mixed $value * @return static */ - public function bucketScript($params) + public function bucketScript($value): static { - return $this->node(BucketScript::create(is_string($params) ? ['buckets_path' => $params] : $params)); + return $this->node(BucketScript::create(is_string($value) ? ['buckets_path' => $value] : $value)); } } diff --git a/src/DSL/Aggs/Pipeline/AvgBucket.php b/src/DSL/Aggs/Pipeline/AvgBucket.php index ec494cb..dabce8a 100644 --- a/src/DSL/Aggs/Pipeline/AvgBucket.php +++ b/src/DSL/Aggs/Pipeline/AvgBucket.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** - * (Optional) The value to use when the aggregation is missing a value. + * The value to use when the aggregation is missing a value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Pipeline/BucketScript.php b/src/DSL/Aggs/Pipeline/BucketScript.php index 7df06b0..98eef0b 100644 --- a/src/DSL/Aggs/Pipeline/BucketScript.php +++ b/src/DSL/Aggs/Pipeline/BucketScript.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * The script to execute for each bucket. * - * @param string|callable $script + * @param string|callable $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', $script); + return $this->addProperty('script', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } } diff --git a/src/DSL/Aggs/Pipeline/CumulativeSum.php b/src/DSL/Aggs/Pipeline/CumulativeSum.php index a484122..3287ab2 100644 --- a/src/DSL/Aggs/Pipeline/CumulativeSum.php +++ b/src/DSL/Aggs/Pipeline/CumulativeSum.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } } diff --git a/src/DSL/Aggs/Pipeline/Derivative.php b/src/DSL/Aggs/Pipeline/Derivative.php index d087cc7..1f32506 100644 --- a/src/DSL/Aggs/Pipeline/Derivative.php +++ b/src/DSL/Aggs/Pipeline/Derivative.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** * The unit for the derivative when the histogram uses time units. * - * @param string $unit + * @param string $value * @return static */ - public function unit($unit) + public function unit(string $value): static { - return $this->addProperty('unit', $unit); + return $this->addProperty('unit', $value); } } diff --git a/src/DSL/Aggs/Pipeline/MaxBucket.php b/src/DSL/Aggs/Pipeline/MaxBucket.php index ea5942c..e762188 100644 --- a/src/DSL/Aggs/Pipeline/MaxBucket.php +++ b/src/DSL/Aggs/Pipeline/MaxBucket.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** - * (Optional) The value to use when the aggregation is missing a value. + * The value to use when the aggregation is missing a value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Pipeline/MinBucket.php b/src/DSL/Aggs/Pipeline/MinBucket.php index 7cf0cbe..dcb4634 100644 --- a/src/DSL/Aggs/Pipeline/MinBucket.php +++ b/src/DSL/Aggs/Pipeline/MinBucket.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** - * (Optional) The value to use when the aggregation is missing a value. + * The value to use when the aggregation is missing a value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Pipeline/StatsBucket.php b/src/DSL/Aggs/Pipeline/StatsBucket.php index 8eb4520..93649b1 100644 --- a/src/DSL/Aggs/Pipeline/StatsBucket.php +++ b/src/DSL/Aggs/Pipeline/StatsBucket.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** - * (Optional) The value to use when the aggregation is missing a value. + * The value to use when the aggregation is missing a value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Aggs/Pipeline/SumBucket.php b/src/DSL/Aggs/Pipeline/SumBucket.php index 07a68d0..74dc3c2 100644 --- a/src/DSL/Aggs/Pipeline/SumBucket.php +++ b/src/DSL/Aggs/Pipeline/SumBucket.php @@ -1,5 +1,7 @@ addProperty('buckets_path', $path); + return $this->addProperty('buckets_path', $value); } /** * Policy to apply when gaps are found in the data. * - * @param string $policy + * @param string $value * @return static */ - public function gapPolicy($policy) + public function gapPolicy(string $value): static { - return $this->addProperty('gap_policy', $policy); + return $this->addProperty('gap_policy', $value); } /** * Format for the output value. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** - * (Optional) The value to use when the aggregation is missing a value. + * The value to use when the aggregation is missing a value. * - * @param mixed $missing + * @param mixed $value * @return static */ - public function missing($missing) + public function missing($value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Node.php b/src/DSL/Node.php index 7967e4d..e47e4af 100644 --- a/src/DSL/Node.php +++ b/src/DSL/Node.php @@ -1,5 +1,7 @@ |null */ - protected $_properties; + protected ?array $_properties = null; /** - * Raw scalar value stored separately from properties. + * Scalar value stored separately from properties. * When set, toArray() outputs shorthand (field => value) if no extra * properties exist, or promotes it under $_valueKey when properties are present. * * @var scalar|null */ - protected $_rawValue; + protected int|float|string|bool|null $_value = null; /** - * The key used when promoting $_rawValue into the properties array. + * The key used when promoting $_value into the properties array. * Override in subclasses that use a different key (e.g. 'query' for match queries). * * @var string */ - protected $_valueKey = 'value'; + protected string $_valueKey = 'value'; /** * Whether to use a field name as the top-level attribute of a node. * * @var bool */ - protected $_isPropertyField = false; + protected bool $_fieldKeyed = false; /** * The field name used as the top-level attribute of a node. * * @var string */ - protected $_field; + protected string $_field; /** * Whether the node supports multiple clauses. * * @var bool */ - protected $_multi = false; + protected bool $_multi = false; /** * The Elasticsearch query or aggregation type identifier. * * @var string */ - protected $_key; + protected string $_key; /** * Initialize the node. @@ -72,101 +76,120 @@ abstract class Node * - new Term('status', fn($t) => ...) — K,V closure * - new Term([...]) — array properties * - new Term(fn($t) => ...) — closure + * - new Script('_score * ...') — bare scalar (whole node value) * - new Term() — empty * - * @param mixed $field Properties, field name, closure, or null + * @param mixed $field Properties, field name, closure, scalar value, or null * @param mixed $value Value/properties/closure when using two-arg mode */ public function __construct($field = null, $value = null) { if ($value !== null) { - // Two-arg mode: field + value - if ($value instanceof Closure) { - $value($this); - } elseif (is_scalar($value)) { - $this->_rawValue = $value; - $this->_properties = []; - } else { - $this->_properties = $value; - } - if ($this->_isPropertyField) { - $this->field($field); - } + $this->fromKeyValue($field, $value); } elseif ($field instanceof Closure) { - // Single-arg closure - $field($this); - } elseif ($this->_isPropertyField && is_array($field)) { - // Array shorthand: ['field_name' => value] - foreach ($field as $key => $val) { - $this->field($key); - if (is_scalar($val)) { - $this->_rawValue = $val; - $this->_properties = []; - } else { - $this->_properties = $val; - } - break; - } - } elseif ($this->_isPropertyField && is_scalar($field)) { - $this->_rawValue = $field; - $this->_properties = []; - } else { + $this->fromClosure($field); + } elseif ($this->_fieldKeyed && is_array($field)) { + $this->fromArrayField($field); + } elseif (is_scalar($field)) { + $this->fromScalar($field); + } elseif (is_array($field)) { $this->_properties = $field; } } /** - * Set whether this node uses a field name as the top-level attribute. + * Initialize from a field-value pair. * - * @param bool $isPropertyField - * @return static + * @param mixed $field + * @param mixed $value */ - protected function isPropertyField($isPropertyField) + protected function fromKeyValue($field, $value): void { - $this->_isPropertyField = $isPropertyField; - return $this; + if ($value instanceof Closure) { + $value($this); + } elseif (is_scalar($value)) { + $this->_value = $value; + $this->_properties = []; + } elseif (is_array($value)) { + $this->_properties = $value; + } + if ($this->_fieldKeyed) { + $this->field($field); + } } /** - * Whether the node supports multiple clauses. + * Initialize from a closure. * - * @param bool $multi - * @return static + * @param Closure $closure */ - protected function multi($multi) + protected function fromClosure(Closure $closure): void { - $this->_multi = $multi; - return $this; + $closure($this); } /** - * Set whether the node supports multiple clauses. + * Initialize from a single-element array where key is field name. * - * @param bool $multi + * @param array $field + */ + protected function fromArrayField(array $field): void + { + foreach ($field as $key => $val) { + $this->field($key); + if (is_scalar($val)) { + $this->_value = $val; + $this->_properties = []; + } elseif (is_array($val)) { + $this->_properties = $val; + } + break; + } + } + + /** + * Initialize from a scalar value. + * + * @param mixed $value + */ + protected function fromScalar($value): void + { + $this->_value = $value; + $this->_properties = []; + } + + /** + * Set whether this node uses a field name as the top-level attribute. + * + * @param bool $fieldKeyed * @return static */ - protected function setMulti($multi) + protected function fieldKeyed(bool $fieldKeyed): static { - $this->_multi = $multi; + $this->_fieldKeyed = $fieldKeyed; return $this; } /** * Whether the node supports multiple clauses. * - * @return bool + * @param bool $multi + * @return static */ - protected function isMulti() + protected function multi(bool $multi): static { - return $this->_multi; + $this->_multi = $multi; + return $this; } /** * Get the Elasticsearch type identifier. * + * @internal + * * @return string */ - public function key() + public function key(): string { return $this->_key; } @@ -177,9 +200,9 @@ public function key() * @param string $field * @return static */ - public function field($field) + public function field($field): static { - if ($this->_isPropertyField) { + if ($this->_fieldKeyed) { $this->_field = $field; } else { $this->_properties['field'] = $field; @@ -195,7 +218,7 @@ public function field($field) * @param bool $append * @return static */ - public function addProperty($attribute, $value, $append = false) + protected function addProperty($attribute, $value, $append = false): static { if ($append) { $this->_properties[$attribute][] = $value; @@ -215,7 +238,7 @@ public function addProperty($attribute, $value, $append = false) * @param mixed $value * @return static */ - public static function create($field = null, $value = null) + public static function create($field = null, $value = null): static { if ($value === null && $field instanceof static) { return $field; @@ -229,7 +252,7 @@ public static function create($field = null, $value = null) * @param array $properties * @return array */ - protected function resolveProperties(array $properties) + protected function resolveProperties(array $properties): array { foreach ($properties as $key => $property) { if ($property instanceof Query) { @@ -249,28 +272,27 @@ protected function resolveProperties(array $properties) * Serialize to an Elasticsearch DSL array. * * Recursively resolves nested Query and Node instances. - * When _isPropertyField is true, wraps properties under the field name. + * When _fieldKeyed is true, wraps properties under the field name. * * @return array|mixed */ public function toArray() { - if ($this->_rawValue !== null) { - if (empty($this->_properties)) { - $properties = $this->_rawValue; + if ($this->_value !== null) { + $props = $this->_properties ?? []; + if ($props === []) { + $properties = $this->_value; } else { - $properties = $this->resolveProperties($this->_properties); + $properties = $this->resolveProperties($props); if (!isset($properties[$this->_valueKey])) { - $properties = array_merge([$this->_valueKey => $this->_rawValue], $properties); + $properties = array_merge([$this->_valueKey => $this->_value], $properties); } } } else { - $properties = is_array($this->_properties) - ? $this->resolveProperties($this->_properties) - : $this->_properties; + $properties = $this->_properties === null ? null : $this->resolveProperties($this->_properties); } - if ($this->_isPropertyField) { + if ($this->_fieldKeyed) { return [$this->_field => $properties]; } return $properties; @@ -283,9 +305,11 @@ public function toArray() * @param int $depth * @return string */ - public function toJson($flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, $depth = 512) + public function toJson(int $flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, int $depth = 512): string { - return json_encode($this->toArray(), $flags, $depth); + $json = json_encode($this->toArray(), $flags, $depth); + + return $json === false ? '' : $json; } /** @@ -293,7 +317,7 @@ public function toJson($flags = JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT, $dep * * @return string */ - public function __toString() + public function __toString(): string { return $this->toJson(); } @@ -302,11 +326,11 @@ public function __toString() * Floating point number used to decrease or increase * the relevance scores of a query. Defaults to 1.0. * - * @param float $boost + * @param float $value * @return static */ - public function boost($boost) + public function boost($value): static { - return $this->addProperty('boost', $boost); + return $this->addProperty('boost', $value); } } diff --git a/src/DSL/Param.php b/src/DSL/Param.php index 49c0dd8..44eee44 100644 --- a/src/DSL/Param.php +++ b/src/DSL/Param.php @@ -1,5 +1,7 @@ */ - protected $_params = []; + protected array $_params = []; /** * Check if a search request parameter has been set. @@ -20,7 +22,7 @@ trait Param * @param string $key * @return bool */ - public function hasParam($key) + public function hasParam($key): bool { return array_key_exists($key, $this->_params); } @@ -29,12 +31,12 @@ public function hasParam($key) * Defines the maximum number of documents to return. * Defaults to 10. * - * @param int $size + * @param int $value * @return $this */ - public function size($size) + public function size($value): static { - $this->_params['size'] = $size; + $this->_params['size'] = $value; return $this; } @@ -42,12 +44,12 @@ public function size($size) * The starting document offset. * Defaults to 0. * - * @param int $from + * @param int $value * @return $this */ - public function from($from) + public function from($value): static { - $this->_params['from'] = $from; + $this->_params['from'] = $value; return $this; } @@ -55,12 +57,12 @@ public function from($from) * Specifies the period of time to wait for * a response from each shard. * - * @param string $timeout + * @param string $value * @return $this */ - public function timeout($timeout) + public function timeout($value): static { - $this->_params['timeout'] = $timeout; + $this->_params['timeout'] = $value; return $this; } @@ -68,12 +70,12 @@ public function timeout($timeout) * Minimum relevance score required for a document * to be included in the result set. * - * @param float $minScore + * @param float $value * @return $this */ - public function minScore($minScore) + public function minScore($value): static { - $this->_params['min_score'] = $minScore; + $this->_params['min_score'] = $value; return $this; } @@ -81,12 +83,12 @@ public function minScore($minScore) * Maximum number of documents to collect for * each shard, upon reaching which the query execution will terminate early. * - * @param int $terminateAfter + * @param int $value * @return $this */ - public function terminateAfter($terminateAfter) + public function terminateAfter($value): static { - $this->_params['terminate_after'] = $terminateAfter; + $this->_params['terminate_after'] = $value; return $this; } @@ -94,12 +96,12 @@ public function terminateAfter($terminateAfter) * If true, returns detailed information about * score computation as part of a hit. * - * @param bool $explain + * @param bool $value * @return $this */ - public function explain($explain) + public function explain($value): static { - $this->_params['explain'] = $explain; + $this->_params['explain'] = $value; return $this; } @@ -107,24 +109,24 @@ public function explain($explain) * If true, returns document version as part * of a hit. * - * @param bool $version + * @param bool $value * @return $this */ - public function version($version) + public function version($value): static { - $this->_params['version'] = $version; + $this->_params['version'] = $value; return $this; } /** * If true, the query is profiled. * - * @param bool $profile + * @param bool $value * @return $this */ - public function profile($profile) + public function profile($value): static { - $this->_params['profile'] = $profile; + $this->_params['profile'] = $value; return $this; } @@ -132,12 +134,12 @@ public function profile($profile) * Number of hits matching the query to count * accurately. Defaults to 10,000. * - * @param bool|int $trackTotalHits + * @param bool|int $value * @return $this */ - public function trackTotalHits($trackTotalHits) + public function trackTotalHits($value): static { - $this->_params['track_total_hits'] = $trackTotalHits; + $this->_params['track_total_hits'] = $value; return $this; } @@ -145,32 +147,36 @@ public function trackTotalHits($trackTotalHits) * If true, returns sequence number and primary * term of the last modification of each hit. * - * @param bool $seqNoPrimaryTerm + * @param bool $value * @return $this */ - public function seqNoPrimaryTerm($seqNoPrimaryTerm) + public function seqNoPrimaryTerm($value): static { - $this->_params['seq_no_primary_term'] = $seqNoPrimaryTerm; + $this->_params['seq_no_primary_term'] = $value; return $this; } /** - * Sorts the response by the given criteria. + * Sorts the response by the given criteria. Appends to the sort list; + * multiple calls chain together. * - * - sort('price', 'asc') — field + order, supports chaining - * - sort([['price' => 'asc']]) — raw ES array format + * - sort('price', 'asc') — field + order + * - sort('price', ['order' => 'asc', 'mode' => 'avg']) — field + options * - sort('_score') — field without direction + * - sort([['price' => 'asc'], ['age' => 'desc']]) — raw ES list, each spec appended * * @param string|array $field - * @param string|null $order + * @param string|array|null $order * @return $this */ - public function sort($field, $order = null) + public function sort($field, $order = null): static { if ($order !== null) { $this->_params['sort'][] = [$field => $order]; } elseif (is_array($field)) { - $this->_params['sort'] = $field; + foreach ($field as $spec) { + $this->_params['sort'][] = $spec; + } } else { $this->_params['sort'][] = $field; } @@ -181,24 +187,24 @@ public function sort($field, $order = null) * Indicates which source fields are returned * for the search hits. * - * @param array|string $source + * @param array|string $value * @return $this */ - public function source($source) + public function source($value): static { - $this->_params['_source'] = $source; + $this->_params['_source'] = $value; return $this; } /** * Sort values used to paginate results. * - * @param array $searchAfter + * @param array $value * @return $this */ - public function searchAfter($searchAfter) + public function searchAfter($value): static { - $this->_params['search_after'] = $searchAfter; + $this->_params['search_after'] = $value; return $this; } @@ -206,37 +212,37 @@ public function searchAfter($searchAfter) * Controls which stored fields are returned * as part of a hit. * - * @param array $storedFields + * @param array $value * @return $this */ - public function storedFields($storedFields) + public function storedFields($value): static { - $this->_params['stored_fields'] = $storedFields; + $this->_params['stored_fields'] = $value; return $this; } /** * Returns docvalue fields as part of a hit. * - * @param array $docvalueFields + * @param array $value * @return $this */ - public function docvalueFields($docvalueFields) + public function docvalueFields($value): static { - $this->_params['docvalue_fields'] = $docvalueFields; + $this->_params['docvalue_fields'] = $value; return $this; } /** - * Boosts the _score of documents from - * specified indices. + * Boosts the _score of documents from specified indices. + * Appends to the indices_boost list; multiple calls chain together. * - * @param array $indicesBoost + * @param array $value * @return $this */ - public function indicesBoost($indicesBoost) + public function indicesBoost($value): static { - $this->_params['indices_boost'] = [$indicesBoost]; + $this->_params['indices_boost'][] = $value; return $this; } @@ -244,12 +250,12 @@ public function indicesBoost($indicesBoost) * If true, compute and return _score even when * sorting on a field. Defaults to false. * - * @param bool $trackScores + * @param bool $value * @return $this */ - public function trackScores($trackScores) + public function trackScores($value): static { - $this->_params['track_scores'] = $trackScores; + $this->_params['track_scores'] = $value; return $this; } @@ -257,74 +263,74 @@ public function trackScores($trackScores) * Returns values from fields in the search response. * Supports field alias fields and array fields. * - * @param array $fields + * @param array $value * @return $this */ - public function fields($fields) + public function fields($value): static { - $this->_params['fields'] = $fields; + $this->_params['fields'] = $value; return $this; } /** * Limits the search to a point in time (PIT). * - * @param array $pit + * @param array $value * @return $this */ - public function pit($pit) + public function pit($value): static { - $this->_params['pit'] = $pit; + $this->_params['pit'] = $value; return $this; } /** - * (Optional) Filter applied after query and aggregation execution. + * Filter applied after query and aggregation execution. * Accepts a closure, array, or Query object. * - * @param mixed $filter + * @param mixed $value * @return $this */ - public function postFilter($filter) + public function postFilter($value): static { - $this->_params['post_filter'] = Query::create($filter); + $this->_params['post_filter'] = Query::create($value); return $this; } /** - * (Optional) Collapse search results by field value. + * Collapse search results by field value. * - * @param mixed $collapse + * @param mixed $value * @return $this */ - public function collapse($collapse) + public function collapse($value): static { - $this->_params['collapse'] = Params\Collapse::create($collapse); + $this->_params['collapse'] = Params\Collapse::create($value); return $this; } /** - * (Optional) Rescore the top documents with a secondary query. + * Rescore the top documents with a secondary query. * - * @param mixed $rescore + * @param mixed $value * @return $this */ - public function rescore($rescore) + public function rescore($value): static { - $this->_params['rescore'] = Params\Rescore::create($rescore); + $this->_params['rescore'] = Params\Rescore::create($value); return $this; } /** - * (Optional) Highlight search matches in field values. + * Highlight search matches in field values. * Supports chaining — fields are merged across calls. * - * @param mixed $highlight + * @param mixed $value * @return $this */ - public function highlight($highlight) + public function highlight($value): static { - $new = Params\Highlight::create($highlight); + $new = Params\Highlight::create($value); if (isset($this->_params['highlight']) && $this->_params['highlight'] instanceof Params\Highlight) { // Merge new fields into existing highlight @@ -336,9 +342,9 @@ public function highlight($highlight) } // Merge other properties (pre_tags, post_tags, etc) — last wins if (is_array($new->_properties)) { - foreach ($new->_properties as $key => $value) { + foreach ($new->_properties as $key => $val) { if ($key !== 'fields') { - $existing->addProperty($key, $value); + $existing->addProperty($key, $val); } } } @@ -350,43 +356,43 @@ public function highlight($highlight) } /** - * (Optional) Search suggestions based on term, completion, or phrase. + * Search suggestions based on term, completion, or phrase. * - * @param mixed $suggest + * @param mixed $value * @return $this */ - public function suggest($suggest) + public function suggest($value): static { - $this->_params['suggest'] = Params\Suggest::create($suggest); + $this->_params['suggest'] = Params\Suggest::create($value); return $this; } /** * Returns script evaluation values for each hit. * - * @param array $scriptFields + * @param array $value * @return $this */ - public function scriptFields($scriptFields) + public function scriptFields($value): static { - $this->_params['script_fields'] = $scriptFields; + $this->_params['script_fields'] = $value; return $this; } /** * Runtime field definitions used in the search request. * - * @param array $runtimeMappings + * @param array $value * @return $this */ - public function runtimeMappings($runtimeMappings) + public function runtimeMappings($value): static { - $this->_params['runtime_mappings'] = $runtimeMappings; + $this->_params['runtime_mappings'] = $value; return $this; } /** - * (Optional) Performs a k-nearest neighbor (kNN) search on a dense_vector field. + * Performs a k-nearest neighbor (kNN) search on a dense_vector field. * Supports chaining — multiple calls append knn clauses as an array. * * - knn(array) — raw ES structure @@ -397,7 +403,7 @@ public function runtimeMappings($runtimeMappings) * @param array|null $queryVector * @return $this */ - public function knn($knn, $queryVector = null) + public function knn($knn, $queryVector = null): static { if (is_string($knn) && $queryVector !== null) { $node = (new Params\Knn())->field($knn)->queryVector($queryVector); diff --git a/src/DSL/Params/Collapse.php b/src/DSL/Params/Collapse.php index 1b01b73..8f242da 100644 --- a/src/DSL/Params/Collapse.php +++ b/src/DSL/Params/Collapse.php @@ -1,5 +1,7 @@ field($field); @@ -32,24 +34,13 @@ public static function create($field = null, $value = null) } /** - * The field to collapse the result set on. - * - * @param string $field - * @return static - */ - public function field($field) - { - return $this->addProperty('field', $field); - } - - /** - * (Optional) Expand each collapsed top hit with the inner_hits option. + * Expand each collapsed top hit with the inner_hits option. * * @param string $name * @param mixed $hits * @return static */ - public function innerHits($name, $hits = null) + public function innerHits(string $name, $hits = null): static { return $this->addProperty('inner_hits', ['name' => $name] + (array)$hits); } @@ -57,11 +48,11 @@ public function innerHits($name, $hits = null) /** * Maximum number of concurrent group searches. * - * @param int $max + * @param int $value * @return static */ - public function maxConcurrentGroupSearches($max) + public function maxConcurrentGroupSearches(int $value): static { - return $this->addProperty('max_concurrent_group_searches', $max); + return $this->addProperty('max_concurrent_group_searches', $value); } } diff --git a/src/DSL/Params/Highlight.php b/src/DSL/Params/Highlight.php index 9f8347b..57e8180 100644 --- a/src/DSL/Params/Highlight.php +++ b/src/DSL/Params/Highlight.php @@ -1,5 +1,7 @@ field($field); @@ -34,13 +36,13 @@ public static function create($field = null, $value = null) } /** - * (Required) Add a field to highlight. Empty settings produces `{}`. + * Add a field to highlight. Empty settings produces `{}`. * * @param string $field * @param array $settings * @return static */ - public function field($field, $settings = []) + public function field($field, array $settings = []): static { $value = empty($settings) ? new stdClass() : $settings; $this->_properties['fields'][$field] = $value; @@ -50,144 +52,144 @@ public function field($field, $settings = []) /** * Opening HTML tags for highlighted snippets. * - * @param array $tags + * @param array $value * @return static */ - public function preTags($tags) + public function preTags(array $value): static { - return $this->addProperty('pre_tags', $tags); + return $this->addProperty('pre_tags', $value); } /** * Closing HTML tags for highlighted snippets. * - * @param array $tags + * @param array $value * @return static */ - public function postTags($tags) + public function postTags(array $value): static { - return $this->addProperty('post_tags', $tags); + return $this->addProperty('post_tags', $value); } /** * Size of a highlighted fragment. Defaults to 100. * - * @param int $size + * @param int $value * @return static */ - public function fragmentSize($size) + public function fragmentSize(int $value): static { - return $this->addProperty('fragment_size', $size); + return $this->addProperty('fragment_size', $value); } /** * Maximum number of fragments to return. * - * @param int $num + * @param int $value * @return static */ - public function numberOfFragments($num) + public function numberOfFragments(int $value): static { - return $this->addProperty('number_of_fragments', $num); + return $this->addProperty('number_of_fragments', $value); } /** * Highlighter encoder: html or default. * - * @param string $encoder + * @param string $value * @return static */ - public function encoder($encoder) + public function encoder(string $value): static { - return $this->addProperty('encoder', $encoder); + return $this->addProperty('encoder', $value); } /** * Sort order for highlighted fragments: score or none. * - * @param string $order + * @param string $value * @return static */ - public function order($order) + public function order(string $value): static { - return $this->addProperty('order', $order); + return $this->addProperty('order', $value); } /** - * (Optional) Highlight against a query other than the search query. + * Highlight against a query other than the search query. * - * @param mixed $query + * @param mixed $value * @return static */ - public function highlightQuery($query) + public function highlightQuery($value): static { - return $this->addProperty('highlight_query', Query::create($query)); + return $this->addProperty('highlight_query', Query::create($value)); } /** * Highlighter type: unified, plain, or fvh. * - * @param string $type + * @param string $value * @return static */ - public function type($type) + public function type(string $value): static { - return $this->addProperty('type', $type); + return $this->addProperty('type', $value); } /** * Boundary scanner: chars, sentence, or word. * - * @param string $scanner + * @param string $value * @return static */ - public function boundaryScanner($scanner) + public function boundaryScanner(string $value): static { - return $this->addProperty('boundary_scanner', $scanner); + return $this->addProperty('boundary_scanner', $value); } /** * Locale for the boundary scanner. * - * @param string $locale + * @param string $value * @return static */ - public function boundaryScannerLocale($locale) + public function boundaryScannerLocale(string $value): static { - return $this->addProperty('boundary_scanner_locale', $locale); + return $this->addProperty('boundary_scanner_locale', $value); } /** * Maximum distance for the boundary scanner. * - * @param int $max + * @param int $value * @return static */ - public function boundaryMaxScan($max) + public function boundaryMaxScan(int $value): static { - return $this->addProperty('boundary_max_scan', $max); + return $this->addProperty('boundary_max_scan', $value); } /** * Size of snippet when no matching fragment is found. * - * @param int $size + * @param int $value * @return static */ - public function noMatchSize($size) + public function noMatchSize(int $value): static { - return $this->addProperty('no_match_size', $size); + return $this->addProperty('no_match_size', $value); } /** * Fragmenter: simple or span (plain highlighter only). * - * @param string $fragmenter + * @param string $value * @return static */ - public function fragmenter($fragmenter) + public function fragmenter(string $value): static { - return $this->addProperty('fragmenter', $fragmenter); + return $this->addProperty('fragmenter', $value); } public function toArray() diff --git a/src/DSL/Params/Knn.php b/src/DSL/Params/Knn.php index 01f80c2..c033394 100644 --- a/src/DSL/Params/Knn.php +++ b/src/DSL/Params/Knn.php @@ -1,5 +1,7 @@ addProperty('field', $field); - } + protected string $_key = 'knn'; /** * The query vector to search for. * - * @param array $vector + * @param array $value * @return static */ - public function queryVector($vector) + public function queryVector(array $value): static { - return $this->addProperty('query_vector', $vector); + return $this->addProperty('query_vector', $value); } /** * Number of nearest neighbors to return as top hits. * Defaults to the search request's size. * - * @param int $k + * @param int $value * @return static */ - public function k($k) + public function k(int $value): static { - return $this->addProperty('k', $k); + return $this->addProperty('k', $value); } /** * Number of candidates to evaluate per shard. * Defaults to max(k * 4, 50). * - * @param int $num + * @param int $value * @return static */ - public function numCandidates($num) + public function numCandidates(int $value): static { - return $this->addProperty('num_candidates', $num); + return $this->addProperty('num_candidates', $value); } /** * Minimum similarity threshold for a vector to be * considered a match. * - * @param float $similarity - * @return static - */ - public function similarity($similarity) - { - return $this->addProperty('similarity', $similarity); - } - - /** - * Boost value for the kNN score. - * - * @param float $boost + * @param float $value * @return static */ - public function boost($boost) + public function similarity(float $value): static { - return $this->addProperty('boost', $boost); + return $this->addProperty('similarity', $value); } /** - * (Optional) Pre-filter applied during kNN search. Accepts a closure, + * Pre-filter applied during kNN search. Accepts a closure, * array, or Query object. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', Query::create($filter)); + return $this->addProperty('filter', Query::create($value)); } /** - * (Optional) Inner hits configuration for nested kNN search. + * Inner hits configuration for nested kNN search. * - * @param mixed $innerHits + * @param mixed $value * @return static */ - public function innerHits($innerHits) + public function innerHits($value): static { - return $this->addProperty('inner_hits', $innerHits); + return $this->addProperty('inner_hits', $value); } /** - * (Optional) Rescore vector configuration for quantized vector rescoring. + * Rescore vector configuration for quantized vector rescoring. * - * @param array $rescoreVector + * @param array $value * @return static */ - public function rescoreVector($rescoreVector) + public function rescoreVector(array $value): static { - return $this->addProperty('rescore_vector', $rescoreVector); + return $this->addProperty('rescore_vector', $value); } public function toArray() diff --git a/src/DSL/Params/Rescore.php b/src/DSL/Params/Rescore.php index cef3e20..632ea45 100644 --- a/src/DSL/Params/Rescore.php +++ b/src/DSL/Params/Rescore.php @@ -1,5 +1,7 @@ addProperty('window_size', $size); + return $this->addProperty('window_size', $value); } /** - * (Required) The query to use for rescoring. + * The query to use for rescoring. * - * @param mixed $query + * @param mixed $value * @return static */ - public function query($query) + public function query($value): static { - $this->_properties['query']['rescore_query'] = Query::create($query); + $this->_properties['query']['rescore_query'] = Query::create($value); return $this; } /** * Weight of the rescore query. Defaults to 1.0. * - * @param float $weight + * @param float $value * @return static */ - public function rescoreQueryWeight($weight) + public function rescoreQueryWeight(float $value): static { - $this->_properties['query']['rescore_query_weight'] = $weight; + $this->_properties['query']['rescore_query_weight'] = $value; return $this; } /** * Weight of the original query. Defaults to 1.0. * - * @param float $weight + * @param float $value * @return static */ - public function queryWeight($weight) + public function queryWeight(float $value): static { - $this->_properties['query']['query_weight'] = $weight; + $this->_properties['query']['query_weight'] = $value; return $this; } @@ -66,12 +68,12 @@ public function queryWeight($weight) * How scores are combined. Valid values: total, * multiply, max, avg. Defaults to total. * - * @param string $mode + * @param string $value * @return static */ - public function scoreMode($mode) + public function scoreMode(string $value): static { - $this->_properties['query']['score_mode'] = $mode; + $this->_properties['query']['score_mode'] = $value; return $this; } diff --git a/src/DSL/Params/Suggest.php b/src/DSL/Params/Suggest.php index 79eef40..1b10c6e 100644 --- a/src/DSL/Params/Suggest.php +++ b/src/DSL/Params/Suggest.php @@ -1,5 +1,7 @@ ['field' => $field]]; if ($text !== null) { @@ -35,10 +37,10 @@ public function term($alias, $field, $text = null) * * @param string $alias * @param string $field - * @param string|null $prefix + * @param ?string $prefix * @return static */ - public function completion($alias, $field, $prefix = null) + public function completion(string $alias, string $field, ?string $prefix = null): static { $suggest = ['completion' => ['field' => $field]]; if ($prefix !== null) { @@ -52,10 +54,10 @@ public function completion($alias, $field, $prefix = null) * * @param string $alias * @param string $field - * @param string|null $text + * @param ?string $text * @return static */ - public function phrase($alias, $field, $text = null) + public function phrase(string $alias, string $field, ?string $text = null): static { $suggest = ['phrase' => ['field' => $field]]; if ($text !== null) { diff --git a/src/DSL/Queries/Compound.php b/src/DSL/Queries/Compound.php index a6a2836..90e92d8 100644 --- a/src/DSL/Queries/Compound.php +++ b/src/DSL/Queries/Compound.php @@ -1,5 +1,7 @@ bool(function (Boolean $b) { $b->must(function (Query $q) { $q->match('title', 'test') }) }) * - * @param callable|Boolean|array $bool + * @param callable|Boolean|array $value * @return $this */ - public function bool($bool) + public function bool($value): static { - if (is_array($bool)) { + if (is_array($value)) { $boolean = new Boolean(); - foreach ($bool as $clause => $val) { + foreach ($value as $clause => $val) { $method = $clause === 'must_not' ? 'mustNot' : $clause; if ($val instanceof \Closure || $val instanceof Query) { $boolean->$method($val); @@ -40,20 +42,20 @@ public function bool($bool) } return $this->addQuery($boolean); } - return $this->addQuery(Boolean::create($bool)); + return $this->addQuery(Boolean::create($value)); } /** * Add a boosting query. * - * @param callable|Boosting|array $boosting + * @param callable|Boosting|array $value * @return $this */ - public function boosting($boosting) + public function boosting($value): static { - if (is_array($boosting)) { + if (is_array($value)) { $b = new Boosting(); - foreach ($boosting as $key => $val) { + foreach ($value as $key => $val) { if (($key === 'positive' || $key === 'negative') && ($val instanceof \Closure || $val instanceof Query)) { $b->$key($val); @@ -63,20 +65,20 @@ public function boosting($boosting) } return $this->addQuery($b); } - return $this->addQuery(Boosting::create($boosting)); + return $this->addQuery(Boosting::create($value)); } /** * Add a constant_score query. * - * @param callable|ConstantScore|array $constantScore + * @param callable|ConstantScore|array $value * @return $this */ - public function constantScore($constantScore) + public function constantScore($value): static { - if (is_array($constantScore)) { + if (is_array($value)) { $cs = new ConstantScore(); - foreach ($constantScore as $key => $val) { + foreach ($value as $key => $val) { if ($key === 'filter' && ($val instanceof \Closure || $val instanceof Query)) { $cs->filter($val); } else { @@ -85,20 +87,20 @@ public function constantScore($constantScore) } return $this->addQuery($cs); } - return $this->addQuery(ConstantScore::create($constantScore)); + return $this->addQuery(ConstantScore::create($value)); } /** * Add a dis_max query. * - * @param callable|DisjunctionMax|array $disMax + * @param callable|DisjunctionMax|array $value * @return $this */ - public function disMax($disMax) + public function disMax($value): static { - if (is_array($disMax)) { + if (is_array($value)) { $dm = new DisjunctionMax(); - foreach ($disMax as $key => $val) { + foreach ($value as $key => $val) { if ($key === 'queries' && ($val instanceof \Closure || $val instanceof Query)) { $dm->queries($val); } else { @@ -107,17 +109,17 @@ public function disMax($disMax) } return $this->addQuery($dm); } - return $this->addQuery(DisjunctionMax::create($disMax)); + return $this->addQuery(DisjunctionMax::create($value)); } /** * Add a function_score query. * - * @param mixed $functionScore + * @param mixed $value * @return $this */ - public function functionScore($functionScore) + public function functionScore($value): static { - return $this->addQuery(FunctionScore::create($functionScore)); + return $this->addQuery(FunctionScore::create($value)); } } diff --git a/src/DSL/Queries/Compound/Boolean.php b/src/DSL/Queries/Compound/Boolean.php index 658fe51..a1bae57 100644 --- a/src/DSL/Queries/Compound/Boolean.php +++ b/src/DSL/Queries/Compound/Boolean.php @@ -1,10 +1,11 @@ addProperty('must', Query::create($must)->multi(true)); - } - - /** - * Append a must clause. Supports multiple calls to incrementally build the bool query. - * Clauses are held in a temporary buffer and merged into the must property during serialization. - * - * @param mixed $must + * @param mixed $value * @return static */ - public function addMust($must) + public function must($value): static { - return $this->pushClause('must', $must); + return $this->addClause('must', $value); } /** * The clause (query) should appear in the matching document. + * Supports multiple calls to incrementally build the bool query. * - * @param mixed $should - * @return static - */ - public function should($should) - { - return $this->addProperty('should', Query::create($should)->multi(true)); - } - - /** - * Append a should clause. Supports multiple calls to incrementally build the bool query. - * - * @param mixed $should + * @param mixed $value * @return static */ - public function addShould($should) + public function should($value): static { - return $this->pushClause('should', $should); + return $this->addClause('should', $value); } /** * The clause (query) must appear in matching documents. However unlike must the score of the query will be ignored. Filter clauses are executed in filter context, meaning that scoring is ignored and clauses are considered for caching. + * Supports multiple calls to incrementally build the bool query. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', Query::create($filter)->multi(true)); - } - - /** - * Append a filter clause. Supports multiple calls to incrementally build the bool query. - * - * @param mixed $filter - * @return static - */ - public function addFilter($filter) - { - return $this->pushClause('filter', $filter); + return $this->addClause('filter', $value); } /** * The clause (query) must not appear in the matching documents. Clauses are executed in filter context meaning that scoring is ignored and clauses are considered for caching. Because scoring is ignored, a score of 0 for all documents is returned. + * Supports multiple calls to incrementally build the bool query. * - * @param mixed $mustNot - * @return static - */ - public function mustNot($mustNot) - { - return $this->addProperty('must_not', Query::create($mustNot)->multi(true)); - } - - /** - * Append a must_not clause. Supports multiple calls to incrementally build the bool query. - * - * @param mixed $mustNot + * @param mixed $value * @return static */ - public function addMustNot($mustNot) + public function mustNot($value): static { - return $this->pushClause('must_not', $mustNot); + return $this->addClause('must_not', $value); } /** @@ -111,11 +71,11 @@ public function addMustNot($mustNot) * * For other valid values, see the minimum_should_match parameter. * - * @param int $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } } diff --git a/src/DSL/Queries/Compound/Boosting.php b/src/DSL/Queries/Compound/Boosting.php index 4279c18..679c041 100644 --- a/src/DSL/Queries/Compound/Boosting.php +++ b/src/DSL/Queries/Compound/Boosting.php @@ -1,5 +1,7 @@ addProperty('positive', Query::create($positive)); + return $this->addProperty('positive', Query::create($value)); } /** * Query used to decrease the relevance score of matching documents. * - * @param mixed $negative + * @param mixed $value * @return static */ - public function negative($negative) + public function negative($value): static { - return $this->addProperty('negative', Query::create($negative)); + return $this->addProperty('negative', Query::create($value)); } /** * Floating point number between 0 and 1.0 used to decrease the relevance scores of documents matching the negative query. * - * @param float $negativeBoost + * @param float $value * @return static */ - public function negativeBoost($negativeBoost) + public function negativeBoost(float $value): static { - return $this->addProperty('negative_boost', $negativeBoost); + return $this->addProperty('negative_boost', $value); } } diff --git a/src/DSL/Queries/Compound/ConstantScore.php b/src/DSL/Queries/Compound/ConstantScore.php index 4101f4c..1a80442 100644 --- a/src/DSL/Queries/Compound/ConstantScore.php +++ b/src/DSL/Queries/Compound/ConstantScore.php @@ -1,5 +1,7 @@ addProperty('filter', Query::create($query)); + return $this->addProperty('filter', Query::create($value)); } } diff --git a/src/DSL/Queries/Compound/DisjunctionMax.php b/src/DSL/Queries/Compound/DisjunctionMax.php index 520cc02..ce6a5a7 100644 --- a/src/DSL/Queries/Compound/DisjunctionMax.php +++ b/src/DSL/Queries/Compound/DisjunctionMax.php @@ -1,10 +1,11 @@ addProperty('queries', Query::create($queries)->multi(true)); - } - - /** - * Append a query clause. Supports multiple calls to incrementally build. - * - * @param mixed $query + * @param mixed $value * @return static */ - public function addQuery($query) + public function queries($value): static { - return $this->pushClause('queries', $query); + return $this->addClause('queries', $value); } /** * Floating point number between 0 and 1.0 used to increase the relevance scores of documents matching multiple query clauses. Defaults to 0.0. * - * @param float $tieBreaker + * @param float $value * @return static */ - public function tieBreaker($tieBreaker) + public function tieBreaker(float $value): static { - return $this->addProperty('tie_breaker', $tieBreaker); + return $this->addProperty('tie_breaker', $value); } } diff --git a/src/DSL/Queries/Compound/FunctionScore.php b/src/DSL/Queries/Compound/FunctionScore.php index d92fff5..52338e6 100644 --- a/src/DSL/Queries/Compound/FunctionScore.php +++ b/src/DSL/Queries/Compound/FunctionScore.php @@ -1,5 +1,7 @@ addProperty('score_mode', $scoreMode); + return $this->addProperty('score_mode', $value); } /** - * (Optional) Defines how the newly computed function score is combined with the query score. + * Defines how the newly computed function score is combined with the query score. * Options: multiply (default), replace, sum, avg, max, min. * - * @param string $boostMode + * @param string $value * @return static */ - public function boostMode($boostMode) + public function boostMode(string $value): static { - return $this->addProperty('boost_mode', $boostMode); + return $this->addProperty('boost_mode', $value); } /** - * (Optional) Excludes documents that do not meet the specified score threshold. + * Excludes documents that do not meet the specified score threshold. * - * @param float $minScore + * @param float $value * @return static */ - public function minScore($minScore) + public function minScore(float $value): static { - return $this->addProperty('min_score', $minScore); + return $this->addProperty('min_score', $value); } /** - * (Optional) Restricts the new score to not exceed the specified limit. Defaults to FLT_MAX. + * Restricts the new score to not exceed the specified limit. Defaults to FLT_MAX. * - * @param float $maxBoost + * @param float $value * @return static */ - public function maxBoost($maxBoost) + public function maxBoost(float $value): static { - return $this->addProperty('max_boost', $maxBoost); + return $this->addProperty('max_boost', $value); } /** - * (Required) The query to be scored. + * The query to be scored. * - * @param mixed $query + * @param mixed $value * @return static */ - public function query($query) + public function query($value): static { - return $this->addProperty('query', Query::create($query)); + return $this->addProperty('query', Query::create($value)); } /** - * (Optional) Array of score functions to apply. + * Array of score functions to apply. * - * @param array $functions + * @param array $value * @return static */ - public function functions($functions) + public function functions(array $value): static { - return $this->addProperty('functions', $functions); + return $this->addProperty('functions', $value); } /** - * (Optional) Appends a score function to the functions array. + * Appends a score function to the functions array. * - * @param mixed $function + * @param mixed $value * @return static */ - public function addFunction($function) + public function addFunction($value): static { - return $this->addProperty('functions', Function_::create($function), true); + return $this->addProperty('functions', Function_::create($value), true); } /** @@ -114,7 +116,7 @@ public function toArray() $properties = $this->resolveProperties($properties); - if ($this->_isPropertyField) { + if ($this->_fieldKeyed) { return [$this->_field => $properties]; } return $properties; diff --git a/src/DSL/Queries/Compound/Functions/Exp.php b/src/DSL/Queries/Compound/Functions/Exp.php index 97bd842..adf6cc2 100644 --- a/src/DSL/Queries/Compound/Functions/Exp.php +++ b/src/DSL/Queries/Compound/Functions/Exp.php @@ -1,5 +1,7 @@ addProperty('origin', $origin); + return $this->addProperty('origin', $value); } /** - * (Required) Defines the distance from origin + offset at which the computed score will equal the decay parameter. + * Defines the distance from origin + offset at which the computed score will equal the decay parameter. * - * @param mixed $scale + * @param mixed $value * @return static */ - public function scale($scale) + public function scale($value): static { - return $this->addProperty('scale', $scale); + return $this->addProperty('scale', $value); } /** - * (Optional) If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. + * If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. * - * @param mixed $offset + * @param mixed $value * @return static */ - public function offset($offset) + public function offset($value): static { - return $this->addProperty('offset', $offset); + return $this->addProperty('offset', $value); } /** - * (Optional) Defines how documents are scored at the distance given at scale. Defaults to 0.5. + * Defines how documents are scored at the distance given at scale. Defaults to 0.5. * - * @param float $decay + * @param float $value * @return static */ - public function decay($decay) + public function decay(float $value): static { - return $this->addProperty('decay', $decay); + return $this->addProperty('decay', $value); } } diff --git a/src/DSL/Queries/Compound/Functions/FieldValueFactor.php b/src/DSL/Queries/Compound/Functions/FieldValueFactor.php index fdb4f41..8e3b945 100644 --- a/src/DSL/Queries/Compound/Functions/FieldValueFactor.php +++ b/src/DSL/Queries/Compound/Functions/FieldValueFactor.php @@ -1,54 +1,45 @@ addProperty('field', $field); - } + protected string $_key = 'field_value_factor'; /** * Optional factor to multiply the field value with, defaults to 1. * - * @param float $factor + * @param float $value * @return static */ - public function factor($factor) + public function factor(float $value): static { - return $this->addProperty('factor', $factor); + return $this->addProperty('factor', $value); } /** * Modifier to apply to the field value, can be one of: none, log, log1p, log2p, ln, ln1p, ln2p, square, sqrt, or reciprocal. Defaults to none. * - * @param string $modifier + * @param string $value * @return static */ - public function modifier($modifier) + public function modifier(string $value): static { - return $this->addProperty('modifier', $modifier); + return $this->addProperty('modifier', $value); } /** * Value used if the document doesn’t have that field. The modifier and factor are still applied to it as though it were read from the document. * - * @param mixed $missing + * @param string|int|float|bool $value * @return static */ - public function missing($missing) + public function missing(string|int|float|bool $value): static { - return $this->addProperty('missing', $missing); + return $this->addProperty('missing', $value); } } diff --git a/src/DSL/Queries/Compound/Functions/Function_.php b/src/DSL/Queries/Compound/Functions/Function_.php index cd09444..dfbdbdb 100644 --- a/src/DSL/Queries/Compound/Functions/Function_.php +++ b/src/DSL/Queries/Compound/Functions/Function_.php @@ -1,5 +1,7 @@ addProperty('filter', Query::create($filter)); + return $this->addProperty('filter', Query::create($value)); } /** - * (Optional) Multiplies the score by the provided weight value. + * Multiplies the score by the provided weight value. * - * @param float $weight + * @param float $value * @return static */ - public function weight($weight) + public function weight(float $value): static { - return $this->addProperty('weight', $weight); + return $this->addProperty('weight', $value); } /** - * (Optional) Generates uniformly distributed random scores from 0 up to but not including 1. + * Generates uniformly distributed random scores from 0 up to but not including 1. * - * @param mixed $randomScore + * @param mixed $value * @return static */ - public function randomScore($randomScore = null) + public function randomScore($value = null): static { - return $this->addProperty('random_score', RandomScore::create($randomScore)); + return $this->addProperty('random_score', RandomScore::create($value)); } /** - * (Optional) Wraps another query and customizes the scoring using a script. + * Wraps another query and customizes the scoring using a script. * - * @param mixed $scriptScore + * @param mixed $value * @return static */ - public function scriptScore($scriptScore) + public function scriptScore($value): static { - return $this->addProperty('script_score', ScriptScore::create($scriptScore)); + return $this->addProperty('script_score', ScriptScore::create($value)); } /** - * (Optional) Sets the script for script_score using a Script object or closure. + * Sets the script for script_score using a Script object or closure. * - * @param mixed $script + * @param mixed $value * @return static */ - public function script($script) + public function script($value): static { - $scriptScore = (new ScriptScore())->script($script); - return $this->addProperty('script_score', $scriptScore); + $valueScore = (new ScriptScore())->script($value); + return $this->addProperty('script_score', $valueScore); } /** - * (Optional) Uses a numeric field value to influence the score. + * Uses a numeric field value to influence the score. * * @param mixed $field - * @param mixed $fieldValueFactor + * @param mixed $value * @return static */ - public function fieldValueFactor($field, $fieldValueFactor = null) + public function fieldValueFactor($field, $value = null): static { - return $this->addProperty('field_value_factor', FieldValueFactor::create($field, $fieldValueFactor)); + return $this->addProperty('field_value_factor', FieldValueFactor::create($field, $value)); } /** - * (Optional) Scores documents using normal (Gaussian) decay based on distance from an origin point. + * Scores documents using normal (Gaussian) decay based on distance from an origin point. * * @param mixed $field - * @param mixed $gauss + * @param mixed $value * @return static */ - public function gauss($field, $gauss = null) + public function gauss($field, $value = null): static { - return $this->addProperty('gauss', Gauss::create($field, $gauss)); + return $this->addProperty('gauss', Gauss::create($field, $value)); } /** - * (Optional) Scores documents using linear decay based on distance from an origin point. + * Scores documents using linear decay based on distance from an origin point. * * @param mixed $field - * @param mixed $linear + * @param mixed $value * @return static */ - public function linear($field, $linear = null) + public function linear($field, $value = null): static { - return $this->addProperty('linear', Linear::create($field, $linear)); + return $this->addProperty('linear', Linear::create($field, $value)); } /** - * (Optional) Scores documents using exponential decay based on distance from an origin point. + * Scores documents using exponential decay based on distance from an origin point. * * @param mixed $field - * @param mixed $exp + * @param mixed $value * @return static */ - public function exp($field, $exp = null) + public function exp($field, $value = null): static { - return $this->addProperty('exp', Exp::create($field, $exp)); + return $this->addProperty('exp', Exp::create($field, $value)); } /** diff --git a/src/DSL/Queries/Compound/Functions/Gauss.php b/src/DSL/Queries/Compound/Functions/Gauss.php index 5bd3fa4..d88dae3 100644 --- a/src/DSL/Queries/Compound/Functions/Gauss.php +++ b/src/DSL/Queries/Compound/Functions/Gauss.php @@ -1,5 +1,7 @@ addProperty('origin', $origin); + return $this->addProperty('origin', $value); } /** - * (Required) Defines the distance from origin + offset at which the computed score will equal the decay parameter. + * Defines the distance from origin + offset at which the computed score will equal the decay parameter. * - * @param mixed $scale + * @param mixed $value * @return static */ - public function scale($scale) + public function scale($value): static { - return $this->addProperty('scale', $scale); + return $this->addProperty('scale', $value); } /** - * (Optional) If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. + * If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. * - * @param mixed $offset + * @param mixed $value * @return static */ - public function offset($offset) + public function offset($value): static { - return $this->addProperty('offset', $offset); + return $this->addProperty('offset', $value); } /** - * (Optional) Defines how documents are scored at the distance given at scale. Defaults to 0.5. + * Defines how documents are scored at the distance given at scale. Defaults to 0.5. * - * @param float $decay + * @param float $value * @return static */ - public function decay($decay) + public function decay(float $value): static { - return $this->addProperty('decay', $decay); + return $this->addProperty('decay', $value); } } diff --git a/src/DSL/Queries/Compound/Functions/Linear.php b/src/DSL/Queries/Compound/Functions/Linear.php index 32fe187..8d64265 100644 --- a/src/DSL/Queries/Compound/Functions/Linear.php +++ b/src/DSL/Queries/Compound/Functions/Linear.php @@ -1,5 +1,7 @@ addProperty('origin', $origin); + return $this->addProperty('origin', $value); } /** - * (Required) Defines the distance from origin + offset at which the computed score will equal the decay parameter. + * Defines the distance from origin + offset at which the computed score will equal the decay parameter. * - * @param mixed $scale + * @param mixed $value * @return static */ - public function scale($scale) + public function scale($value): static { - return $this->addProperty('scale', $scale); + return $this->addProperty('scale', $value); } /** - * (Optional) If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. + * If defined, the decay function will only compute for documents with a distance greater than this offset. Defaults to 0. * - * @param mixed $offset + * @param mixed $value * @return static */ - public function offset($offset) + public function offset($value): static { - return $this->addProperty('offset', $offset); + return $this->addProperty('offset', $value); } /** - * (Optional) Defines how documents are scored at the distance given at scale. Defaults to 0.5. + * Defines how documents are scored at the distance given at scale. Defaults to 0.5. * - * @param float $decay + * @param float $value * @return static */ - public function decay($decay) + public function decay(float $value): static { - return $this->addProperty('decay', $decay); + return $this->addProperty('decay', $value); } } diff --git a/src/DSL/Queries/Compound/Functions/RandomScore.php b/src/DSL/Queries/Compound/Functions/RandomScore.php index 4a98aa9..a7ae078 100644 --- a/src/DSL/Queries/Compound/Functions/RandomScore.php +++ b/src/DSL/Queries/Compound/Functions/RandomScore.php @@ -1,5 +1,7 @@ addProperty('seed', $seed); - } + protected string $_key = 'random_score'; /** - * (Optional) Field used in combination with the seed to compute reproducible random scores. + * Seed value for reproducible random scores. * - * @param string $field + * @param mixed $value * @return static */ - public function field($field) + public function seed($value): static { - return $this->addProperty('field', $field); + return $this->addProperty('seed', $value); } /** diff --git a/src/DSL/Queries/Compound/Functions/ScriptScore.php b/src/DSL/Queries/Compound/Functions/ScriptScore.php index 8175b3b..e2c9f7f 100644 --- a/src/DSL/Queries/Compound/Functions/ScriptScore.php +++ b/src/DSL/Queries/Compound/Functions/ScriptScore.php @@ -1,5 +1,7 @@ addProperty('script', Script::create($script)); + return $this->addProperty('script', Script::create($value)); } } diff --git a/src/DSL/Queries/FullText.php b/src/DSL/Queries/FullText.php index 666c3fb..7056c9b 100644 --- a/src/DSL/Queries/FullText.php +++ b/src/DSL/Queries/FullText.php @@ -1,5 +1,7 @@ addQuery(Intervals::create($field, $value)); } @@ -38,7 +40,7 @@ public function intervals($field, $value = null) * @param callable|string|array $value * @return $this */ - public function match($field, $value = null) + public function match($field, $value = null): static { return $this->addQuery(Match_::create($field, $value)); } @@ -50,7 +52,7 @@ public function match($field, $value = null) * @param mixed $value * @return $this */ - public function matchPhrase($field, $value = null) + public function matchPhrase($field, $value = null): static { return $this->addQuery(MatchPhrase::create($field, $value)); } @@ -62,7 +64,7 @@ public function matchPhrase($field, $value = null) * @param mixed $value * @return $this */ - public function matchPhrasePrefix($field, $value = null) + public function matchPhrasePrefix($field, $value = null): static { return $this->addQuery(MatchPhrasePrefix::create($field, $value)); } @@ -74,7 +76,7 @@ public function matchPhrasePrefix($field, $value = null) * @param mixed $value * @return $this */ - public function matchBoolPrefix($field, $value = null) + public function matchBoolPrefix($field, $value = null): static { return $this->addQuery(MatchBoolPrefix::create($field, $value)); } @@ -87,7 +89,7 @@ public function matchBoolPrefix($field, $value = null) * @param callable|MultiMatch|array $value * @return $this */ - public function multiMatch($value) + public function multiMatch($value): static { return $this->addQuery(MultiMatch::create($value)); } @@ -98,7 +100,7 @@ public function multiMatch($value) * @param mixed $value * @return $this */ - public function combinedFields($value) + public function combinedFields($value): static { return $this->addQuery(CombinedFields::create($value)); } @@ -106,22 +108,22 @@ public function combinedFields($value) /** * Add a query_string query. * - * @param mixed $queryString + * @param mixed $value * @return $this */ - public function queryString($queryString) + public function queryString($value): static { - return $this->addQuery(QueryString::create($queryString)); + return $this->addQuery(QueryString::create($value)); } /** * Add a simple_query_string query. * - * @param mixed $simpleQueryString + * @param mixed $value * @return $this */ - public function simpleQueryString($simpleQueryString) + public function simpleQueryString($value): static { - return $this->addQuery(SimpleQueryString::create($simpleQueryString)); + return $this->addQuery(SimpleQueryString::create($value)); } } diff --git a/src/DSL/Queries/FullText/CombinedFields.php b/src/DSL/Queries/FullText/CombinedFields.php index 4ea5799..5b29314 100644 --- a/src/DSL/Queries/FullText/CombinedFields.php +++ b/src/DSL/Queries/FullText/CombinedFields.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** @@ -32,48 +34,48 @@ public function query($query) * patterns are allowed. Only text fields are supported, and they must all * have the same search analyzer. * - * @param array $fields + * @param array $value * @return static */ - public function fields($fields) + public function fields(array $value): static { - return $this->addProperty('fields', $fields); + return $this->addProperty('fields', $value); } /** * If true, match phrase queries are automatically * created for multi-term synonyms. Defaults to true. * - * @param bool $autoGenerateSynonymsPhraseQuery + * @param bool $value * @return static */ - public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery) + public function autoGenerateSynonymsPhraseQuery(bool $value): static { - return $this->addProperty('auto_generate_synonyms_phrase_query', $autoGenerateSynonymsPhraseQuery); + return $this->addProperty('auto_generate_synonyms_phrase_query', $value); } /** * Boolean logic used to interpret text in the query * value. Valid values are: or (Default), and. * - * @param string $operator + * @param string $value * @return static */ - public function operator($operator) + public function operator(string $value): static { - return $this->addProperty('operator', $operator); + return $this->addProperty('operator', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** @@ -81,11 +83,11 @@ public function minimumShouldMatch($minimumShouldMatch) * analyzer removes all tokens, such as when using a stop filter. * Valid values are: none (Default), all. * - * @param string $zeroTermsQuery + * @param string $value * @return static */ - public function zeroTermsQuery($zeroTermsQuery) + public function zeroTermsQuery(string $value): static { - return $this->addProperty('zero_terms_query', $zeroTermsQuery); + return $this->addProperty('zero_terms_query', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals.php b/src/DSL/Queries/FullText/Intervals.php index 077167f..198f6ec 100644 --- a/src/DSL/Queries/FullText/Intervals.php +++ b/src/DSL/Queries/FullText/Intervals.php @@ -1,5 +1,7 @@ */ - protected $_intervals = []; + protected array $_intervals = []; /** * Add a match rule that matches analyzed text. * - * @param mixed $match + * @param mixed $value * @return static */ - public function match($match) + public function match($value): static { - $this->_intervals[] = Intervals\Match_::create($match); + $this->_intervals[] = Intervals\Match_::create($value); return $this; } @@ -34,24 +36,37 @@ public function match($match) * Add a prefix rule that matches terms that start with a specified set * of characters. * - * @param mixed $prefix + * @param mixed $value * @return static */ - public function prefix($prefix) + public function prefix($value): static { - $this->_intervals[] = Intervals\Prefix::create($prefix); + $this->_intervals[] = Intervals\Prefix::create($value); return $this; } /** * Add a wildcard rule that matches terms using a wildcard pattern. * - * @param mixed $wildcard + * @param mixed $value + * @return static + */ + public function wildcard($value): static + { + $this->_intervals[] = Intervals\Wildcard::create($value); + return $this; + } + + /** + * Add a regexp rule that matches terms using a regular expression + * pattern. + * + * @param mixed $value * @return static */ - public function wildcard($wildcard) + public function regexp($value): static { - $this->_intervals[] = Intervals\Wildcard::create($wildcard); + $this->_intervals[] = Intervals\Regexp::create($value); return $this; } @@ -59,24 +74,24 @@ public function wildcard($wildcard) * Add a fuzzy rule that matches terms that are similar to the provided * term, within a defined edit distance. * - * @param mixed $fuzzy + * @param mixed $value * @return static */ - public function fuzzy($fuzzy) + public function fuzzy($value): static { - $this->_intervals[] = Intervals\Fuzzy::create($fuzzy); + $this->_intervals[] = Intervals\Fuzzy::create($value); return $this; } /** * Add a range rule that matches terms that fall within a specified range. * - * @param mixed $range + * @param mixed $value * @return static */ - public function range($range) + public function range($value): static { - $this->_intervals[] = Intervals\Range::create($range); + $this->_intervals[] = Intervals\Range::create($value); return $this; } @@ -84,12 +99,12 @@ public function range($range) * Add an all_of rule that returns matches that span a combination of * other rules. * - * @param mixed $allOf + * @param mixed $value * @return static */ - public function allOf($allOf) + public function allOf($value): static { - $this->_intervals[] = Intervals\AllOf::create($allOf); + $this->_intervals[] = Intervals\AllOf::create($value); return $this; } @@ -97,12 +112,12 @@ public function allOf($allOf) * Add an any_of rule that returns intervals produced by any of its * sub-rules. * - * @param mixed $anyOf + * @param mixed $value * @return static */ - public function anyOf($anyOf) + public function anyOf($value): static { - $this->_intervals[] = Intervals\AnyOf::create($anyOf); + $this->_intervals[] = Intervals\AnyOf::create($value); return $this; } @@ -127,7 +142,7 @@ public function toArray() $properties = $resolved; } - if ($this->_isPropertyField) { + if ($this->_fieldKeyed) { return [$this->_field => $properties]; } return $properties; diff --git a/src/DSL/Queries/FullText/Intervals/AllOf.php b/src/DSL/Queries/FullText/Intervals/AllOf.php index 817a3ec..c139ace 100644 --- a/src/DSL/Queries/FullText/Intervals/AllOf.php +++ b/src/DSL/Queries/FullText/Intervals/AllOf.php @@ -1,5 +1,7 @@ isPropertyField(false) + $value = Intervals::create($value) + ->fieldKeyed(false) ->multi(true); - return $this->addProperty('intervals', $intervals); + return $this->addProperty('intervals', $value); } /** * Append an interval rule. Supports multiple calls to incrementally build. * - * @param mixed $interval + * @param mixed $value * @return static */ - public function addInterval($interval) + public function addInterval($value): static { if (!isset($this->_properties['intervals'])) { - $this->_properties['intervals'] = (new Intervals())->isPropertyField(false)->multi(true); + $this->_properties['intervals'] = (new Intervals())->fieldKeyed(false)->multi(true); } $target = $this->_properties['intervals']; - if ($interval instanceof \Closure) { - $interval($target); - } elseif ($interval instanceof Node) { - $target->addQuery($interval); + if ($value instanceof \Closure) { + $value($target); } return $this; } @@ -52,35 +52,35 @@ public function addInterval($interval) * terms. Intervals produced by the rules further apart than this are not * considered matches. Defaults to -1 (no restriction). * - * @param int $maxGaps + * @param int $value * @return static */ - public function maxGaps($maxGaps) + public function maxGaps(int $value): static { - return $this->addProperty('max_gaps', $maxGaps); + return $this->addProperty('max_gaps', $value); } /** * If true, intervals produced by the rules should * appear in the order in which they are specified. Defaults to false. * - * @param bool $ordered + * @param bool $value * @return static */ - public function ordered($ordered) + public function ordered(bool $value): static { - return $this->addProperty('ordered', $ordered); + return $this->addProperty('ordered', $value); } /** * Rule used to filter returned * intervals. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', $filter); + return $this->addProperty('filter', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals/AnyOf.php b/src/DSL/Queries/FullText/Intervals/AnyOf.php index b1da1e6..b5fd8e9 100644 --- a/src/DSL/Queries/FullText/Intervals/AnyOf.php +++ b/src/DSL/Queries/FullText/Intervals/AnyOf.php @@ -1,5 +1,7 @@ isPropertyField(false) + $value = Intervals::create($value) + ->fieldKeyed(false) ->multi(true); - return $this->addProperty('intervals', $intervals); + return $this->addProperty('intervals', $value); } /** * Append an interval rule. Supports multiple calls to incrementally build. * - * @param mixed $interval + * @param mixed $value * @return static */ - public function addInterval($interval) + public function addInterval($value): static { if (!isset($this->_properties['intervals'])) { - $this->_properties['intervals'] = (new Intervals())->isPropertyField(false)->multi(true); + $this->_properties['intervals'] = (new Intervals())->fieldKeyed(false)->multi(true); } $target = $this->_properties['intervals']; - if ($interval instanceof \Closure) { - $interval($target); - } elseif ($interval instanceof Node) { - $target->addQuery($interval); + if ($value instanceof \Closure) { + $value($target); } return $this; } @@ -50,11 +50,11 @@ public function addInterval($interval) * Rule used to filter returned * intervals. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', Filter::create($filter)); + return $this->addProperty('filter', Filter::create($value)); } } diff --git a/src/DSL/Queries/FullText/Intervals/Filter.php b/src/DSL/Queries/FullText/Intervals/Filter.php index 940b5a2..5957a91 100644 --- a/src/DSL/Queries/FullText/Intervals/Filter.php +++ b/src/DSL/Queries/FullText/Intervals/Filter.php @@ -1,5 +1,7 @@ addProperty('after', Query::create($after)); + return $this->addProperty('after', Query::create($value)); } /** * Query used to return intervals that occur before * an interval from the filter rule. * - * @param mixed $before + * @param mixed $value * @return static */ - public function before($before) + public function before($value): static { - return $this->addProperty('before', Query::create($before)); + return $this->addProperty('before', Query::create($value)); } /** * Query used to return intervals contained by an * interval from the filter rule. * - * @param mixed $containedBy + * @param mixed $value * @return static */ - public function containedBy($containedBy) + public function containedBy($value): static { - return $this->addProperty('contained_by', Query::create($containedBy)); + return $this->addProperty('contained_by', Query::create($value)); } /** * Query used to return intervals that contain an * interval from the filter rule. * - * @param mixed $containing + * @param mixed $value * @return static */ - public function containing($containing) + public function containing($value): static { - return $this->addProperty('containing', Query::create($containing)); + return $this->addProperty('containing', Query::create($value)); } /** * Query used to return intervals that do not * contain an interval from the filter rule. * - * @param mixed $notContaining + * @param mixed $value * @return static */ - public function notContaining($notContaining) + public function notContaining($value): static { - return $this->addProperty('not_containing', Query::create($notContaining)); + return $this->addProperty('not_containing', Query::create($value)); } /** * Query used to return intervals that overlap * with an interval from the filter rule. * - * @param mixed $overlapping + * @param mixed $value * @return static */ - public function overlapping($overlapping) + public function overlapping($value): static { - return $this->addProperty('overlapping', Query::create($overlapping)); + return $this->addProperty('overlapping', Query::create($value)); } /** @@ -89,35 +93,35 @@ public function overlapping($overlapping) * This script must return a boolean value, true or false. The script can * use the interval variable with start, end, and gaps methods. * - * @param mixed $script + * @param mixed $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', Script::create($script)); + return $this->addProperty('script', Script::create($value)); } /** * Query used to return intervals that are not * contained by an interval from the filter rule. * - * @param mixed $notContainedBy + * @param mixed $value * @return static */ - public function notContainedBy($notContainedBy) + public function notContainedBy($value): static { - return $this->addProperty('not_contained_by', Query::create($notContainedBy)); + return $this->addProperty('not_contained_by', Query::create($value)); } /** * Query used to return intervals that do not * overlap with an interval from the filter rule. * - * @param mixed $notOverlapping + * @param mixed $value * @return static */ - public function notOverlapping($notOverlapping) + public function notOverlapping($value): static { - return $this->addProperty('not_overlapping', Query::create($notOverlapping)); + return $this->addProperty('not_overlapping', Query::create($value)); } } diff --git a/src/DSL/Queries/FullText/Intervals/Fuzzy.php b/src/DSL/Queries/FullText/Intervals/Fuzzy.php index 1a62ed3..419849b 100644 --- a/src/DSL/Queries/FullText/Intervals/Fuzzy.php +++ b/src/DSL/Queries/FullText/Intervals/Fuzzy.php @@ -1,5 +1,7 @@ addProperty('term', $term); + return $this->addProperty('term', $value); } /** * Number of beginning characters left unchanged when * creating expansions. Defaults to 0. * - * @param int $prefixLength + * @param int $value * @return static */ - public function prefixLength($prefixLength) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $prefixLength); + return $this->addProperty('prefix_length', $value); } /** * Indicates whether edits include transpositions of * two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $transpositions + * @param bool $value * @return static */ - public function transpositions($transpositions) + public function transpositions(bool $value): static { - return $this->addProperty('transpositions', $transpositions); + return $this->addProperty('transpositions', $value); } /** * Maximum edit distance allowed for matching. * Defaults to auto. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Analyzer used to normalize the term. Defaults to the * top-level field's analyzer. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** @@ -77,11 +79,11 @@ public function analyzer($analyzer) * than the top-level field. The term is normalized using the search * analyzer from this field. * - * @param string $useField + * @param string $value * @return static */ - public function useField($useField) + public function useField(string $value): static { - return $this->addProperty('use_field', $useField); + return $this->addProperty('use_field', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals/Match_.php b/src/DSL/Queries/FullText/Intervals/Match_.php index 6c7e8f9..89657c4 100644 --- a/src/DSL/Queries/FullText/Intervals/Match_.php +++ b/src/DSL/Queries/FullText/Intervals/Match_.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** @@ -30,47 +32,47 @@ public function query($query) * Defaults to -1 (no restriction). If set to 0, the terms must appear * next to each other. * - * @param int $maxGaps + * @param int $value * @return static */ - public function maxGaps($maxGaps) + public function maxGaps(int $value): static { - return $this->addProperty('max_gaps', $maxGaps); + return $this->addProperty('max_gaps', $value); } /** * If true, matching terms must appear in their * specified order. Defaults to false. * - * @param bool $ordered + * @param bool $value * @return static */ - public function ordered($ordered = false) + public function ordered(bool $value = false): static { - return $this->addProperty('ordered', $ordered); + return $this->addProperty('ordered', $value); } /** * Analyzer used to analyze terms in the query. * Defaults to the top-level field's analyzer. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * An optional interval filter. * - * @param mixed $filter + * @param mixed $value * @return static */ - public function filter($filter) + public function filter($value): static { - return $this->addProperty('filter', Filter::create($filter)); + return $this->addProperty('filter', Filter::create($value)); } /** @@ -78,11 +80,11 @@ public function filter($filter) * than the top-level field. Terms are analyzed using the search analyzer * from this field. * - * @param string $useField + * @param string $value * @return static */ - public function useField($useField) + public function useField(string $value): static { - return $this->addProperty('use_field', $useField); + return $this->addProperty('use_field', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals/Prefix.php b/src/DSL/Queries/FullText/Intervals/Prefix.php index 2ff087e..38e7d60 100644 --- a/src/DSL/Queries/FullText/Intervals/Prefix.php +++ b/src/DSL/Queries/FullText/Intervals/Prefix.php @@ -1,5 +1,7 @@ addProperty('prefix', $prefix); + return $this->addProperty('prefix', $value); } /** * Analyzer used to normalize the prefix. Defaults to * the top-level field's analyzer. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** @@ -42,11 +44,11 @@ public function analyzer($analyzer) * than the top-level field. The prefix is normalized using the search * analyzer from this field. * - * @param string $userField + * @param string $value * @return static */ - public function useField($userField) + public function useField(string $value): static { - return $this->addProperty('use_field', $userField); + return $this->addProperty('use_field', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals/Range.php b/src/DSL/Queries/FullText/Intervals/Range.php index f40a01b..b931ef9 100644 --- a/src/DSL/Queries/FullText/Intervals/Range.php +++ b/src/DSL/Queries/FullText/Intervals/Range.php @@ -1,9 +1,11 @@ addProperty('gte', $gte); + return $this->addProperty('gte', $value); } /** - * (Optional) Greater than the specified value. + * Greater than the specified value. * - * @param mixed $gt + * @param string|int|float|bool $value * @return static */ - public function gt($gt) + public function gt(string|int|float|bool $value): static { - return $this->addProperty('gt', $gt); + return $this->addProperty('gt', $value); } /** - * (Optional) Less than or equal to the specified value. + * Less than or equal to the specified value. * - * @param mixed $lte + * @param string|int|float|bool $value * @return static */ - public function lte($lte) + public function lte(string|int|float|bool $value): static { - return $this->addProperty('lte', $lte); + return $this->addProperty('lte', $value); } /** - * (Optional) Less than the specified value. + * Less than the specified value. * - * @param mixed $lt + * @param string|int|float|bool $value * @return static */ - public function lt($lt) + public function lt(string|int|float|bool $value): static { - return $this->addProperty('lt', $lt); + return $this->addProperty('lt', $value); } /** * Analyzer used to normalize the range values. * Defaults to the top-level field's analyzer. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * If specified, match intervals from this field rather * than the top-level field. * - * @param string $useField + * @param string $value * @return static */ - public function useField($useField) + public function useField(string $value): static { - return $this->addProperty('use_field', $useField); + return $this->addProperty('use_field', $value); } } diff --git a/src/DSL/Queries/FullText/Intervals/Regexp.php b/src/DSL/Queries/FullText/Intervals/Regexp.php new file mode 100644 index 0000000..306cbbe --- /dev/null +++ b/src/DSL/Queries/FullText/Intervals/Regexp.php @@ -0,0 +1,55 @@ +addProperty('pattern', $value); + } + + /** + * Analyzer used to normalize the pattern. Defaults to + * the top-level field's analyzer. + * + * @param string $value + * @return static + */ + public function analyzer(string $value): static + { + return $this->addProperty('analyzer', $value); + } + + /** + * If specified, match intervals from this field rather + * than the top-level field. The pattern is normalized using the search + * analyzer from this field. + * + * @param string $value + * @return static + */ + public function useField(string $value): static + { + return $this->addProperty('use_field', $value); + } +} diff --git a/src/DSL/Queries/FullText/Intervals/Wildcard.php b/src/DSL/Queries/FullText/Intervals/Wildcard.php index 0a3174c..92892ab 100644 --- a/src/DSL/Queries/FullText/Intervals/Wildcard.php +++ b/src/DSL/Queries/FullText/Intervals/Wildcard.php @@ -1,5 +1,7 @@ addProperty('pattern', $pattern); + return $this->addProperty('pattern', $value); } /** * Analyzer used to normalize the pattern. Defaults to * the top-level field's analyzer. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** @@ -42,11 +44,11 @@ public function analyzer($analyzer) * than the top-level field. The pattern is normalized using the search * analyzer from this field. * - * @param string $useField + * @param string $value * @return static */ - public function useField($useField) + public function useField(string $value): static { - return $this->addProperty('use_field', $useField); + return $this->addProperty('use_field', $value); } } diff --git a/src/DSL/Queries/FullText/MatchBoolPrefix.php b/src/DSL/Queries/FullText/MatchBoolPrefix.php index 9d41172..1c4e311 100644 --- a/src/DSL/Queries/FullText/MatchBoolPrefix.php +++ b/src/DSL/Queries/FullText/MatchBoolPrefix.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** * Maximum number of terms to which the last provided * term of the query value will expand. Defaults to 50. * - * @param int $maxExpansions + * @param int $value * @return static */ - public function maxExpansions($maxExpansions) + public function maxExpansions(int $value): static { - return $this->addProperty('max_expansions', $maxExpansions); + return $this->addProperty('max_expansions', $value); } /** * If true, format-based errors, such as providing a * text query value for a numeric field, are ignored. Defaults to false. * - * @param bool $lenient + * @param bool $value * @return static */ - public function lenient($lenient) + public function lenient(bool $value): static { - return $this->addProperty('lenient', $lenient); + return $this->addProperty('lenient', $value); } /** * Analyzer used to convert text in the query value * into tokens. Defaults to the index-time analyzer mapped for the field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** * Maximum edit distance allowed for fuzzy matching. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Number of beginning characters left unchanged for * fuzzy matching. Defaults to 0. * - * @param int $prefixLength + * @param int $value * @return static */ - public function prefixLength($prefixLength) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $prefixLength); + return $this->addProperty('prefix_length', $value); } /** * If true, edits for fuzzy matching include * transpositions of two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $fuzzyTranspositions + * @param bool $value * @return static */ - public function fuzzyTranspositions($fuzzyTranspositions) + public function fuzzyTranspositions(bool $value): static { - return $this->addProperty('fuzzy_transpositions', $fuzzyTranspositions); + return $this->addProperty('fuzzy_transpositions', $value); } /** * Method used to rewrite the query for fuzzy matching. * - * @param string $fuzzyRewrite + * @param string $value * @return static */ - public function fuzzyRewrite($fuzzyRewrite) + public function fuzzyRewrite(string $value): static { - return $this->addProperty('fuzzy_rewrite', $fuzzyRewrite); + return $this->addProperty('fuzzy_rewrite', $value); } /** * Boolean logic used to interpret text in the query * value. Valid values are: OR (Default), AND. * - * @param string $operator + * @param string $value * @return static */ - public function operator($operator) + public function operator(string $value): static { - return $this->addProperty('operator', $operator); + return $this->addProperty('operator', $value); } } diff --git a/src/DSL/Queries/FullText/MatchPhrase.php b/src/DSL/Queries/FullText/MatchPhrase.php index 2ca8a03..921ab50 100644 --- a/src/DSL/Queries/FullText/MatchPhrase.php +++ b/src/DSL/Queries/FullText/MatchPhrase.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** * Analyzer used to convert text in the query value * into tokens. Defaults to the index-time analyzer mapped for the field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * Maximum number of positions allowed between matching * tokens. Defaults to 0. Transposed terms have a slop of 2. * - * @param int $slop + * @param int $value * @return static */ - public function slop($slop) + public function slop(int $value): static { - return $this->addProperty('slop', $slop); + return $this->addProperty('slop', $value); } /** @@ -59,11 +61,11 @@ public function slop($slop) * analyzer removes all tokens, such as when using a stop filter. * Valid values are: none (Default), all. * - * @param string $zeroTermsQuery + * @param string $value * @return static */ - public function zeroTermsQuery($zeroTermsQuery) + public function zeroTermsQuery(string $value): static { - return $this->addProperty('zero_terms_query', $zeroTermsQuery); + return $this->addProperty('zero_terms_query', $value); } } diff --git a/src/DSL/Queries/FullText/MatchPhrasePrefix.php b/src/DSL/Queries/FullText/MatchPhrasePrefix.php index ee99983..9b2fcc6 100644 --- a/src/DSL/Queries/FullText/MatchPhrasePrefix.php +++ b/src/DSL/Queries/FullText/MatchPhrasePrefix.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** * Analyzer used to convert text in the query value * into tokens. Defaults to the index-time analyzer mapped for the field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * Maximum number of terms to which the last provided * term of the query value will expand. Defaults to 50. * - * @param int $maxExpansions + * @param int $value * @return static */ - public function maxExpansions($maxExpansions) + public function maxExpansions(int $value): static { - return $this->addProperty('max_expansions', $maxExpansions); + return $this->addProperty('max_expansions', $value); } /** * Maximum number of positions allowed between matching * tokens. Defaults to 0. Transposed terms have a slop of 2. * - * @param int $slop + * @param int $value * @return static */ - public function slop($slop) + public function slop(int $value): static { - return $this->addProperty('slop', $slop); + return $this->addProperty('slop', $value); } /** @@ -72,11 +74,11 @@ public function slop($slop) * analyzer removes all tokens, such as when using a stop filter. * Valid values are: none (Default), all. * - * @param string $zeroTermsQuery + * @param string $value * @return static */ - public function zeroTermsQuery($zeroTermsQuery) + public function zeroTermsQuery(string $value): static { - return $this->addProperty('zero_terms_query', $zeroTermsQuery); + return $this->addProperty('zero_terms_query', $value); } } diff --git a/src/DSL/Queries/FullText/Match_.php b/src/DSL/Queries/FullText/Match_.php index f57ca8b..70a0466 100644 --- a/src/DSL/Queries/FullText/Match_.php +++ b/src/DSL/Queries/FullText/Match_.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** * Analyzer used to convert the text in the query value * into tokens. Defaults to the index-time analyzer mapped for the field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * If true, match phrase queries are automatically * created for multi-term synonyms. Defaults to true. * - * @param bool $autoGenerateSynonymsPhraseQuery + * @param bool $value * @return static */ - public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery) + public function autoGenerateSynonymsPhraseQuery(bool $value): static { - return $this->addProperty('auto_generate_synonyms_phrase_query', $autoGenerateSynonymsPhraseQuery); + return $this->addProperty('auto_generate_synonyms_phrase_query', $value); } /** * Maximum edit distance allowed for matching. * See Fuzziness for valid values and more information. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Maximum number of terms to which the query will * expand. Defaults to 50. * - * @param int $maxExpansions + * @param int $value * @return static */ - public function maxExpansions($maxExpansions) + public function maxExpansions(int $value): static { - return $this->addProperty('max_expansions', $maxExpansions); + return $this->addProperty('max_expansions', $value); } /** * Number of beginning characters left unchanged for * fuzzy matching. Defaults to 0. * - * @param int $prefixLength + * @param int $value * @return static */ - public function prefixLength($prefixLength) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $prefixLength); + return $this->addProperty('prefix_length', $value); } /** * If true, edits for fuzzy matching include * transpositions of two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $fuzzyTranspositions + * @param bool $value * @return static */ - public function fuzzyTranspositions($fuzzyTranspositions) + public function fuzzyTranspositions(bool $value): static { - return $this->addProperty('fuzzy_transpositions', $fuzzyTranspositions); + return $this->addProperty('fuzzy_transpositions', $value); } /** @@ -109,48 +111,48 @@ public function fuzzyTranspositions($fuzzyTranspositions) * parameter is not 0, the match query uses a fuzzy_rewrite method of * top_terms_blended_freqs_${max_expansions} by default. * - * @param string $fuzzyRewrite + * @param string $value * @return static */ - public function fuzzyRewrite($fuzzyRewrite) + public function fuzzyRewrite(string $value): static { - return $this->addProperty('fuzzy_rewrite', $fuzzyRewrite); + return $this->addProperty('fuzzy_rewrite', $value); } /** * If true, format-based errors, such as providing a * text query value for a numeric field, are ignored. Defaults to false. * - * @param bool $lenient + * @param bool $value * @return static */ - public function lenient($lenient) + public function lenient(bool $value): static { - return $this->addProperty('lenient', $lenient); + return $this->addProperty('lenient', $value); } /** * Boolean logic used to interpret text in the query * value. Valid values are: OR (Default), AND. * - * @param string $operator + * @param string $value * @return static */ - public function operator($operator) + public function operator(string $value): static { - return $this->addProperty('operator', $operator); + return $this->addProperty('operator', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** @@ -158,11 +160,11 @@ public function minimumShouldMatch($minimumShouldMatch) * analyzer removes all tokens, such as when using a stop filter. * Valid values are: none (Default), all. * - * @param string $zeroTermsQuery + * @param string $value * @return static */ - public function zeroTermsQuery($zeroTermsQuery) + public function zeroTermsQuery(string $value): static { - return $this->addProperty('zero_terms_query', $zeroTermsQuery); + return $this->addProperty('zero_terms_query', $value); } } diff --git a/src/DSL/Queries/FullText/MultiMatch.php b/src/DSL/Queries/FullText/MultiMatch.php index 768bec5..38f35e3 100644 --- a/src/DSL/Queries/FullText/MultiMatch.php +++ b/src/DSL/Queries/FullText/MultiMatch.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** @@ -30,12 +32,12 @@ public function query($query) * wildcards (*). Individual fields can be boosted with the caret (^) * notation. * - * @param array $fields + * @param array $value * @return static */ - public function fields($fields) + public function fields(array $value): static { - return $this->addProperty('fields', $fields); + return $this->addProperty('fields', $value); } /** @@ -43,130 +45,130 @@ public function fields($fields) * Valid values are: best_fields (Default), most_fields, cross_fields, * phrase, phrase_prefix, bool_prefix. * - * @param string $type + * @param string $value * @return static */ - public function type($type) + public function type(string $value): static { - return $this->addProperty('type', $type); + return $this->addProperty('type', $value); } /** * Boolean logic used to interpret text in the query * value. Valid values are: OR (Default), AND. * - * @param string $operator + * @param string $value * @return static */ - public function operator($operator) + public function operator(string $value): static { - return $this->addProperty('operator', $operator); + return $this->addProperty('operator', $value); } /** * Analyzer used to convert the text in the query value * into tokens. Defaults to the index-time analyzer mapped for the field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** * Floating point number used to decrease or increase * the relevance scores of a query. Defaults to 1.0. * - * @param float $tieBreaker + * @param float $value * @return static */ - public function tieBreaker($tieBreaker) + public function tieBreaker(float $value): static { - return $this->addProperty('tie_breaker', $tieBreaker); + return $this->addProperty('tie_breaker', $value); } /** * Maximum edit distance allowed for fuzzy matching. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Number of beginning characters left unchanged for * fuzzy matching. Defaults to 0. * - * @param int $prefixLength + * @param int $value * @return static */ - public function prefixLength($prefixLength) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $prefixLength); + return $this->addProperty('prefix_length', $value); } /** * Maximum number of terms to which the query will * expand. Defaults to 50. * - * @param int $maxExpansions + * @param int $value * @return static */ - public function maxExpansions($maxExpansions) + public function maxExpansions(int $value): static { - return $this->addProperty('max_expansions', $maxExpansions); + return $this->addProperty('max_expansions', $value); } /** * If true, edits for fuzzy matching include * transpositions of two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $fuzzyTranspositions + * @param bool $value * @return static */ - public function fuzzyTranspositions($fuzzyTranspositions) + public function fuzzyTranspositions(bool $value): static { - return $this->addProperty('fuzzy_transpositions', $fuzzyTranspositions); + return $this->addProperty('fuzzy_transpositions', $value); } /** * Method used to rewrite the query for fuzzy matching. * - * @param string $fuzzyRewrite + * @param string $value * @return static */ - public function fuzzyRewrite($fuzzyRewrite) + public function fuzzyRewrite(string $value): static { - return $this->addProperty('fuzzy_rewrite', $fuzzyRewrite); + return $this->addProperty('fuzzy_rewrite', $value); } /** * If true, format-based errors, such as providing a * text query value for a numeric field, are ignored. Defaults to false. * - * @param bool $lenient + * @param bool $value * @return static */ - public function lenient($lenient) + public function lenient(bool $value): static { - return $this->addProperty('lenient', $lenient); + return $this->addProperty('lenient', $value); } /** @@ -174,35 +176,35 @@ public function lenient($lenient) * analyzer removes all tokens, such as when using a stop filter. * Valid values are: none (Default), all. * - * @param string $zeroTermsQuery + * @param string $value * @return static */ - public function zeroTermsQuery($zeroTermsQuery) + public function zeroTermsQuery(string $value): static { - return $this->addProperty('zero_terms_query', $zeroTermsQuery); + return $this->addProperty('zero_terms_query', $value); } /** * Maximum number of positions allowed between matching * tokens. Defaults to 0. Transposed terms have a slop of 2. * - * @param int $slop + * @param int $value * @return static */ - public function slop($slop) + public function slop(int $value): static { - return $this->addProperty('slop', $slop); + return $this->addProperty('slop', $value); } /** * If true, match phrase queries are automatically * created for multi-term synonyms. Defaults to true. * - * @param bool $autoGenerateSynonymsPhraseQuery + * @param bool $value * @return static */ - public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery) + public function autoGenerateSynonymsPhraseQuery(bool $value): static { - return $this->addProperty('auto_generate_synonyms_phrase_query', $autoGenerateSynonymsPhraseQuery); + return $this->addProperty('auto_generate_synonyms_phrase_query', $value); } } diff --git a/src/DSL/Queries/FullText/QueryString.php b/src/DSL/Queries/FullText/QueryString.php index 8446b3e..b4145fb 100644 --- a/src/DSL/Queries/FullText/QueryString.php +++ b/src/DSL/Queries/FullText/QueryString.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** * Default field to search if no field is provided in * the query string. Supports wildcards (*). Defaults to *. * - * @param string $defaultField + * @param string $value * @return static */ - public function defaultField($defaultField) + public function defaultField(string $value): static { - return $this->addProperty('default_field', $defaultField); + return $this->addProperty('default_field', $value); } /** * If true, the wildcard characters * and ? are allowed * as the first character of the query string. Defaults to true. * - * @param bool $allowLeadingWildcard + * @param bool $value * @return static */ - public function allowLeadingWildcard($allowLeadingWildcard) + public function allowLeadingWildcard(bool $value): static { - return $this->addProperty('allow_leading_wildcard', $allowLeadingWildcard); + return $this->addProperty('allow_leading_wildcard', $value); } /** @@ -54,24 +56,24 @@ public function allowLeadingWildcard($allowLeadingWildcard) * into tokens. Defaults to the index-time analyzer mapped for the * default_field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * If true, match phrase queries are automatically * created for multi-term synonyms. Defaults to true. * - * @param bool $autoGenerateSynonymsPhraseQuery + * @param bool $value * @return static */ - public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery) + public function autoGenerateSynonymsPhraseQuery(bool $value): static { - return $this->addProperty('auto_generate_synonyms_phrase_query', $autoGenerateSynonymsPhraseQuery); + return $this->addProperty('auto_generate_synonyms_phrase_query', $value); } /** @@ -79,119 +81,119 @@ public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery * query string if no operators are specified. Valid values are: * OR (Default), AND. * - * @param string $defaultOperator + * @param string $value * @return static */ - public function defaultOperator($defaultOperator) + public function defaultOperator(string $value): static { - return $this->addProperty('default_operator', $defaultOperator); + return $this->addProperty('default_operator', $value); } /** * If true, enable position increments in queries * constructed from a query_string search. Defaults to true. * - * @param bool $enablePositionIncrements + * @param bool $value * @return static */ - public function enablePositionIncrements($enablePositionIncrements) + public function enablePositionIncrements(bool $value): static { - return $this->addProperty('enable_position_increments', $enablePositionIncrements); + return $this->addProperty('enable_position_increments', $value); } /** * Array of fields to search. Supports * wildcards (*). * - * @param array $fields + * @param array $value * @return static */ - public function fields($fields) + public function fields(array $value): static { - return $this->addProperty('fields', $fields); + return $this->addProperty('fields', $value); } /** * Maximum edit distance allowed for fuzzy matching. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Maximum number of terms to which the query expands * for fuzzy matching. Defaults to 50. * - * @param int $fuzzyMaxExpansions + * @param int $value * @return static */ - public function fuzzyMaxExpansions($fuzzyMaxExpansions) + public function fuzzyMaxExpansions(int $value): static { - return $this->addProperty('fuzzy_max_expansions', $fuzzyMaxExpansions); + return $this->addProperty('fuzzy_max_expansions', $value); } /** * Number of beginning characters left unchanged for * fuzzy matching. Defaults to 0. * - * @param int $fuzzyPrefixLength + * @param int $value * @return static */ - public function fuzzyPrefixLength($fuzzyPrefixLength) + public function fuzzyPrefixLength(int $value): static { - return $this->addProperty('fuzzy_prefix_length', $fuzzyPrefixLength); + return $this->addProperty('fuzzy_prefix_length', $value); } /** * If true, edits for fuzzy matching include * transpositions of two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $fuzzyTranspositions + * @param bool $value * @return static */ - public function fuzzyTranspositions($fuzzyTranspositions) + public function fuzzyTranspositions(bool $value): static { - return $this->addProperty('fuzzy_transpositions', $fuzzyTranspositions); + return $this->addProperty('fuzzy_transpositions', $value); } /** * If true, format-based errors, such as providing a * text value for a numeric field, are ignored. Defaults to false. * - * @param bool $lenient + * @param bool $value * @return static */ - public function lenient($lenient) + public function lenient(bool $value): static { - return $this->addProperty('lenient', $lenient); + return $this->addProperty('lenient', $value); } /** * Maximum number of automaton states required for * the query. Default is 10000. * - * @param int $maxDeterminizedStates + * @param int $value * @return static */ - public function maxDeterminizedStates($maxDeterminizedStates) + public function maxDeterminizedStates(int $value): static { - return $this->addProperty('max_determinized_states', $maxDeterminizedStates); + return $this->addProperty('max_determinized_states', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** @@ -199,12 +201,12 @@ public function minimumShouldMatch($minimumShouldMatch) * string into tokens. Defaults to the search_quote_analyzer mapped for * the default_field. * - * @param string $quoteAnalyzer + * @param string $value * @return static */ - public function quoteAnalyzer($quoteAnalyzer) + public function quoteAnalyzer(string $value): static { - return $this->addProperty('quote_analyzer', $quoteAnalyzer); + return $this->addProperty('quote_analyzer', $value); } /** @@ -212,12 +214,12 @@ public function quoteAnalyzer($quoteAnalyzer) * tokens for phrases. Defaults to 0. If 0, exact phrase matches are * required. Transposed terms have a slop of 2. * - * @param int $phraseSlop + * @param int $value * @return static */ - public function phraseSlop($phraseSlop) + public function phraseSlop(int $value): static { - return $this->addProperty('phrase_slop', $phraseSlop); + return $this->addProperty('phrase_slop', $value); } /** @@ -225,35 +227,35 @@ public function phraseSlop($phraseSlop) * You can use this suffix to use a different analysis method for exact * matches. * - * @param string $quoteFieldSuffix + * @param string $value * @return static */ - public function quoteFieldSuffix($quoteFieldSuffix) + public function quoteFieldSuffix(string $value): static { - return $this->addProperty('quote_field_suffix', $quoteFieldSuffix); + return $this->addProperty('quote_field_suffix', $value); } /** * Method used to rewrite the query. * - * @param string $rewrite + * @param string $value * @return static */ - public function rewrite($rewrite) + public function rewrite(string $value): static { - return $this->addProperty('rewrite', $rewrite); + return $this->addProperty('rewrite', $value); } /** * Coordinated Universal Time (UTC) offset or IANA time * zone used to convert date values in the query string to UTC. * - * @param string $timeZone + * @param string $value * @return static */ - public function timeZone($timeZone) + public function timeZone(string $value): static { - return $this->addProperty('time_zone', $timeZone); + return $this->addProperty('time_zone', $value); } /** @@ -261,35 +263,35 @@ public function timeZone($timeZone) * when searching multiple fields. Valid values are: best_fields (Default), * most_fields, cross_fields, phrase, phrase_prefix, bool_prefix. * - * @param string $type + * @param string $value * @return static */ - public function type($type) + public function type(string $value): static { - return $this->addProperty('type', $type); + return $this->addProperty('type', $value); } /** * If true, the query attempts to analyze wildcard terms * in the query string. Defaults to false. * - * @param bool $analyzeWildcard + * @param bool $value * @return static */ - public function analyzeWildcard($analyzeWildcard) + public function analyzeWildcard(bool $value): static { - return $this->addProperty('analyze_wildcard', $analyzeWildcard); + return $this->addProperty('analyze_wildcard', $value); } /** * Floating point number used to control the scoring of * results when searching multiple fields. Defaults to 0. * - * @param float $tieBreaker + * @param float $value * @return static */ - public function tieBreaker($tieBreaker) + public function tieBreaker(float $value): static { - return $this->addProperty('tie_breaker', $tieBreaker); + return $this->addProperty('tie_breaker', $value); } } diff --git a/src/DSL/Queries/FullText/SimpleQueryString.php b/src/DSL/Queries/FullText/SimpleQueryString.php index 5b475c9..700a93e 100644 --- a/src/DSL/Queries/FullText/SimpleQueryString.php +++ b/src/DSL/Queries/FullText/SimpleQueryString.php @@ -1,5 +1,7 @@ addProperty('query', $query); + return $this->addProperty('query', $value); } /** @@ -30,12 +32,12 @@ public function query($query) * Supports wildcard expressions and per-field boosting with caret (^) * notation. * - * @param array $fields + * @param array $value * @return static */ - public function fields($fields) + public function fields(array $value): static { - return $this->addProperty('fields', $fields); + return $this->addProperty('fields', $value); } /** @@ -43,24 +45,24 @@ public function fields($fields) * query string if no operators are specified. Valid values are: * OR (Default), AND. * - * @param string $defaultOperator + * @param string $value * @return static */ - public function defaultOperator($defaultOperator) + public function defaultOperator(string $value): static { - return $this->addProperty('default_operator', $defaultOperator); + return $this->addProperty('default_operator', $value); } /** * If true, the query attempts to analyze wildcard terms * in the query string. Defaults to false. * - * @param bool $analyzeWildcard + * @param bool $value * @return static */ - public function analyzeWildcard($analyzeWildcard) + public function analyzeWildcard(bool $value): static { - return $this->addProperty('analyze_wildcard', $analyzeWildcard); + return $this->addProperty('analyze_wildcard', $value); } /** @@ -68,96 +70,96 @@ public function analyzeWildcard($analyzeWildcard) * into tokens. Defaults to the index-time analyzer mapped for the * default_field. * - * @param string $analyzer + * @param string $value * @return static */ - public function analyzer($analyzer) + public function analyzer(string $value): static { - return $this->addProperty('analyzer', $analyzer); + return $this->addProperty('analyzer', $value); } /** * If true, the parser creates a match_phrase query * for each multi-position token. Defaults to true. * - * @param bool $autoGenerateSynonymsPhraseQuery + * @param bool $value * @return static */ - public function autoGenerateSynonymsPhraseQuery($autoGenerateSynonymsPhraseQuery) + public function autoGenerateSynonymsPhraseQuery(bool $value): static { - return $this->addProperty('auto_generate_synonyms_phrase_query', $autoGenerateSynonymsPhraseQuery); + return $this->addProperty('auto_generate_synonyms_phrase_query', $value); } /** * List of enabled operators for the simple query string * syntax. Defaults to ALL (all operators). * - * @param string $flags + * @param string $value * @return static */ - public function flags($flags) + public function flags(string $value): static { - return $this->addProperty('flags', $flags); + return $this->addProperty('flags', $value); } /** * Maximum number of terms to which the query expands * for fuzzy matching. Defaults to 50. * - * @param int $fuzzyMaxExpansions + * @param int $value * @return static */ - public function fuzzyMaxExpansions($fuzzyMaxExpansions) + public function fuzzyMaxExpansions(int $value): static { - return $this->addProperty('fuzzy_max_expansions', $fuzzyMaxExpansions); + return $this->addProperty('fuzzy_max_expansions', $value); } /** * Number of beginning characters left unchanged for * fuzzy matching. Defaults to 0. * - * @param int $fuzzyPrefixLength + * @param int $value * @return static */ - public function fuzzyPrefixLength($fuzzyPrefixLength) + public function fuzzyPrefixLength(int $value): static { - return $this->addProperty('fuzzy_prefix_length', $fuzzyPrefixLength); + return $this->addProperty('fuzzy_prefix_length', $value); } /** * If true, edits for fuzzy matching include * transpositions of two adjacent characters (ab -> ba). Defaults to true. * - * @param bool $fuzzyTranspositions + * @param bool $value * @return static */ - public function fuzzyTranspositions($fuzzyTranspositions) + public function fuzzyTranspositions(bool $value): static { - return $this->addProperty('fuzzy_transpositions', $fuzzyTranspositions); + return $this->addProperty('fuzzy_transpositions', $value); } /** * If true, format-based errors, such as providing a * text value for a numeric field, are ignored. Defaults to false. * - * @param bool $lenient + * @param bool $value * @return static */ - public function lenient($lenient) + public function lenient(bool $value): static { - return $this->addProperty('lenient', $lenient); + return $this->addProperty('lenient', $value); } /** * Minimum number of clauses that must match for a * document to be returned. * - * @param string $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** @@ -165,11 +167,11 @@ public function minimumShouldMatch($minimumShouldMatch) * You can use this suffix to use a different analysis method for exact * matches. * - * @param string $quoteFieldSuffix + * @param string $value * @return static */ - public function quoteFieldSuffix($quoteFieldSuffix) + public function quoteFieldSuffix(string $value): static { - return $this->addProperty('quote_field_suffix', $quoteFieldSuffix); + return $this->addProperty('quote_field_suffix', $value); } } diff --git a/src/DSL/Queries/Geo.php b/src/DSL/Queries/Geo.php index ddddf22..51f567d 100644 --- a/src/DSL/Queries/Geo.php +++ b/src/DSL/Queries/Geo.php @@ -1,5 +1,7 @@ addQuery(GeoBoundingBox::create($field, $value)); } @@ -33,7 +35,7 @@ public function geoBoundingBox($field, $value = null) * @param callable|GeoDistance|array $value * @return $this */ - public function geoDistance($value = null) + public function geoDistance($value = null): static { return $this->addQuery(GeoDistance::create($value)); } @@ -45,7 +47,7 @@ public function geoDistance($value = null) * @param mixed $value * @return $this */ - public function geoGrid($field, $value = null) + public function geoGrid($field, $value = null): static { return $this->addQuery(GeoGrid::create($field, $value)); } @@ -57,7 +59,7 @@ public function geoGrid($field, $value = null) * @param mixed $value * @return $this */ - public function geoPolygon($field, $value = null) + public function geoPolygon($field, $value = null): static { return $this->addQuery(GeoPolygon::create($field, $value)); } @@ -69,7 +71,7 @@ public function geoPolygon($field, $value = null) * @param mixed $value * @return $this */ - public function geoShape($field, $value = null) + public function geoShape($field, $value = null): static { return $this->addQuery(GeoShape::create($field, $value)); } diff --git a/src/DSL/Queries/Geo/GeoBoundingBox.php b/src/DSL/Queries/Geo/GeoBoundingBox.php index 728ae91..2502bda 100644 --- a/src/DSL/Queries/Geo/GeoBoundingBox.php +++ b/src/DSL/Queries/Geo/GeoBoundingBox.php @@ -1,5 +1,7 @@ addProperty('top_left', $topLeft); + return $this->addProperty('top_left', $value); } /** * Bottom-right corner of the bounding box. * - * @param mixed $bottomRight + * @param mixed $value * @return static */ - public function bottomRight($bottomRight) + public function bottomRight($value): static { - return $this->addProperty('bottom_right', $bottomRight); + return $this->addProperty('bottom_right', $value); } /** * Top latitude of the bounding box. Can be used instead of topLeft/bottomRight pairs to set values separately. * - * @param float $top + * @param float $value * @return static */ - public function top($top) + public function top(float $value): static { - return $this->addProperty('top', $top); + return $this->addProperty('top', $value); } /** * Left longitude of the bounding box. Can be used instead of topLeft/bottomRight pairs to set values separately. * - * @param float $left + * @param float $value * @return static */ - public function left($left) + public function left(float $value): static { - return $this->addProperty('left', $left); + return $this->addProperty('left', $value); } /** * Bottom latitude of the bounding box. Can be used instead of topLeft/bottomRight pairs to set values separately. * - * @param float $bottom + * @param float $value * @return static */ - public function bottom($bottom) + public function bottom(float $value): static { - return $this->addProperty('bottom', $bottom); + return $this->addProperty('bottom', $value); } /** * Right longitude of the bounding box. Can be used instead of topLeft/bottomRight pairs to set values separately. * - * @param float $right + * @param float $value * @return static */ - public function right($right) + public function right(float $value): static { - return $this->addProperty('right', $right); + return $this->addProperty('right', $value); } /** * Bounding box defined as Well-Known Text (WKT) BBOX format. * - * @param string $wkt + * @param string $value * @return static */ - public function wkt($wkt) + public function wkt(string $value): static { - return $this->addProperty('wkt', $wkt); + return $this->addProperty('wkt', $value); } /** * Top-right corner of the bounding box. Can be used with bottomLeft instead of topLeft/bottomRight. * - * @param mixed $topRight + * @param mixed $value * @return static */ - public function topRight($topRight) + public function topRight($value): static { - return $this->addProperty('top_right', $topRight); + return $this->addProperty('top_right', $value); } /** * Bottom-left corner of the bounding box. Can be used with topRight instead of topLeft/bottomRight. * - * @param mixed $bottomLeft + * @param mixed $value * @return static */ - public function bottomLeft($bottomLeft) + public function bottomLeft($value): static { - return $this->addProperty('bottom_left', $bottomLeft); + return $this->addProperty('bottom_left', $value); } /** * Set to IGNORE_MALFORMED to accept geo points with invalid latitude or longitude, * set to COERCE to also try to infer correct latitude or longitude. Defaults to STRICT. * - * @param string $validationMethod + * @param string $value * @return static */ - public function validationMethod($validationMethod) + public function validationMethod(string $value): static { - return $this->addProperty('validation_method', $validationMethod); + return $this->addProperty('validation_method', $value); } /** * When set to true, the ignore_unmapped option will ignore an unmapped field * and will not match any documents for this query. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/Geo/GeoDistance.php b/src/DSL/Queries/Geo/GeoDistance.php index fca2402..e0ea3f4 100644 --- a/src/DSL/Queries/Geo/GeoDistance.php +++ b/src/DSL/Queries/Geo/GeoDistance.php @@ -1,5 +1,7 @@ addProperty('distance', $distance); + return $this->addProperty('distance', $value); } /** @@ -39,7 +41,7 @@ public function distance($distance) * @param mixed $location * @return static */ - public function location($field, $location) + public function location(string $field, $location): static { return $this->addProperty($field, $location); } @@ -48,36 +50,34 @@ public function location($field, $location) * How to compute the distance. Can either be arc (default) or * plane (faster, but inaccurate on long distances and close to the poles). * - * @param string $distanceType + * @param string $value * @return static */ - public function distanceType($distanceType) + public function distanceType(string $value): static { - return $this->addProperty('distance_type', $distanceType); + return $this->addProperty('distance_type', $value); } /** * Optional name field to identify the query. * - * @param string $_name + * @param string $value * @return static - * @SuppressWarnings(PHPMD.CamelCaseParameterName) - * @SuppressWarnings(PHPMD.CamelCaseVariableName) */ - public function _name($_name) + public function _name(string $value): static { - return $this->addProperty('_name', $_name); + return $this->addProperty('_name', $value); } /** * Set to IGNORE_MALFORMED to accept geo points with invalid latitude or longitude, * set to COERCE to additionally try and infer correct coordinates. Defaults to STRICT. * - * @param string $validationMethod + * @param string $value * @return static */ - public function validationMethod($validationMethod) + public function validationMethod(string $value): static { - return $this->addProperty('validation_method', $validationMethod); + return $this->addProperty('validation_method', $value); } } diff --git a/src/DSL/Queries/Geo/GeoGrid.php b/src/DSL/Queries/Geo/GeoGrid.php index d6fdb98..66337a8 100644 --- a/src/DSL/Queries/Geo/GeoGrid.php +++ b/src/DSL/Queries/Geo/GeoGrid.php @@ -1,5 +1,7 @@ addProperty('geohex', $geohex); + return $this->addProperty('geohex', $value); } /** * The geotile grid key to match (e.g. "6/32/21"). * Usable with geo_point and geo_shape fields. * - * @param string $geotile + * @param string $value * @return static */ - public function geotile($geotile) + public function geotile(string $value): static { - return $this->addProperty('geotile', $geotile); + return $this->addProperty('geotile', $value); } /** * The geohash grid key to match (e.g. "u1"). * Usable with geo_point and geo_shape fields. * - * @param string $geohash + * @param string $value * @return static */ - public function geohash($geohash) + public function geohash(string $value): static { - return $this->addProperty('geohash', $geohash); + return $this->addProperty('geohash', $value); } } diff --git a/src/DSL/Queries/Geo/GeoPolygon.php b/src/DSL/Queries/Geo/GeoPolygon.php index d456e23..bc5b05d 100644 --- a/src/DSL/Queries/Geo/GeoPolygon.php +++ b/src/DSL/Queries/Geo/GeoPolygon.php @@ -1,5 +1,7 @@ > $points + * @param array> $value * @return static */ - public function points($points) + public function points(array $value): static { - return $this->addProperty('points', $points); + return $this->addProperty('points', $value); } /** * Set to IGNORE_MALFORMED to accept geo points with invalid latitude or longitude, * set to COERCE to try and infer correct latitude or longitude, or STRICT (default). * - * @param string $validationMethod + * @param string $value * @return static */ - public function validationMethod($validationMethod) + public function validationMethod(string $value): static { - return $this->addProperty('validation_method', $validationMethod); + return $this->addProperty('validation_method', $value); } /** * When set to true, the ignore_unmapped option will ignore an unmapped field * and will not match any documents for this query. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/Geo/GeoShape.php b/src/DSL/Queries/Geo/GeoShape.php index 638ed51..e600074 100644 --- a/src/DSL/Queries/Geo/GeoShape.php +++ b/src/DSL/Queries/Geo/GeoShape.php @@ -1,5 +1,7 @@ addProperty('shape', $shape); + return $this->addProperty('shape', $value); } /** * Spatial relation operator to use at search time. * Valid values: INTERSECTS (default), DISJOINT, WITHIN, CONTAINS. * - * @param string $relation + * @param string $value * @return static */ - public function relation($relation) + public function relation(string $value): static { - return $this->addProperty('relation', $relation); + return $this->addProperty('relation', $value); } /** * Reference to a pre-indexed shape. Contains id, index, path, and routing fields * to identify the shape document in another index. * - * @param mixed $indexedShape + * @param mixed $value * @return static */ - public function indexedShape($indexedShape) + public function indexedShape($value): static { - return $this->addProperty('indexed_shape', $indexedShape); + return $this->addProperty('indexed_shape', $value); } /** * When set to true, the ignore_unmapped option will ignore an unmapped field * and will not match any documents for this query. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/Joining.php b/src/DSL/Queries/Joining.php index d814469..caf8527 100644 --- a/src/DSL/Queries/Joining.php +++ b/src/DSL/Queries/Joining.php @@ -1,5 +1,7 @@ |null $query optional query when first arg is a path string * @return $this */ - public function nested($path, $query = null) + public function nested($path, $query = null): static { if (is_string($path) && $query !== null) { $nested = Nested::create($path); @@ -39,7 +41,7 @@ public function nested($path, $query = null) * @param callable|Query|array|null $query optional query when first arg is a type string * @return $this */ - public function hasChild($type, $query = null) + public function hasChild($type, $query = null): static { if (is_string($type) && $query !== null) { $hasChild = HasChild::create(); @@ -57,7 +59,7 @@ public function hasChild($type, $query = null) * @param callable|Query|array|null $query optional query when first arg is a parent_type string * @return $this */ - public function hasParent($type, $query = null) + public function hasParent($type, $query = null): static { if (is_string($type) && $query !== null) { $hasParent = HasParent::create(); @@ -71,11 +73,11 @@ public function hasParent($type, $query = null) /** * Add a parent_id query. * - * @param mixed $parentId + * @param mixed $value * @return $this */ - public function parentId($parentId) + public function parentId($value): static { - return $this->addQuery(ParentId::create($parentId)); + return $this->addQuery(ParentId::create($value)); } } diff --git a/src/DSL/Queries/Joining/HasChild.php b/src/DSL/Queries/Joining/HasChild.php index 303cd39..5f6ac72 100644 --- a/src/DSL/Queries/Joining/HasChild.php +++ b/src/DSL/Queries/Joining/HasChild.php @@ -1,5 +1,7 @@ addProperty('type', $type); + return $this->addProperty('type', $value); } /** * Query you wish to run on child documents of the type field. * If a child document matches the search, the query returns the parent document. * - * @param mixed $query + * @param mixed $value * @return static */ - public function query($query) + public function query($value): static { - return $this->addProperty('query', Query::create($query)); + return $this->addProperty('query', Query::create($value)); } /** * Indicates whether to ignore an unmapped type and not return any * documents instead of an error. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } /** * Maximum number of child documents that match the query allowed for a * returned parent document. If the parent document exceeds this limit, it is excluded from the search results. * - * @param int $maxChildren + * @param int $value * @return static */ - public function maxChildren($maxChildren) + public function maxChildren(int $value): static { - return $this->addProperty('max_children', $maxChildren); + return $this->addProperty('max_children', $value); } /** @@ -66,23 +68,23 @@ public function maxChildren($maxChildren) * the query for a returned parent document. If the parent document does not meet this limit, * it is excluded from the search results. * - * @param int $minChildren + * @param int $value * @return static */ - public function minChildren($minChildren) + public function minChildren(int $value): static { - return $this->addProperty('min_children', $minChildren); + return $this->addProperty('min_children', $value); } /** * Indicates how scores for matching child documents affect the root parent * document's relevance score. Valid values: none (default), avg, max, min, sum. * - * @param string $scoreMode + * @param string $value * @return static */ - public function scoreMode($scoreMode) + public function scoreMode(string $value): static { - return $this->addProperty('score_mode', $scoreMode); + return $this->addProperty('score_mode', $value); } } diff --git a/src/DSL/Queries/Joining/HasParent.php b/src/DSL/Queries/Joining/HasParent.php index 17c0c16..c01178b 100644 --- a/src/DSL/Queries/Joining/HasParent.php +++ b/src/DSL/Queries/Joining/HasParent.php @@ -1,5 +1,7 @@ addProperty('parent_type', $parentType); + return $this->addProperty('parent_type', $value); } /** * Query you wish to run on parent documents of the parent_type field. * If a parent document matches the search, the query returns its child documents. * - * @param mixed $query + * @param mixed $value * @return static */ - public function query($query) + public function query($value): static { - return $this->addProperty('query', Query::create($query)); + return $this->addProperty('query', Query::create($value)); } /** * Indicates whether the relevance score of a matching parent document is * aggregated into its child documents. Defaults to false. * - * @param bool $score + * @param bool $value * @return static */ - public function score($score) + public function score(bool $value): static { - return $this->addProperty('score', $score); + return $this->addProperty('score', $value); } /** * Indicates whether to ignore an unmapped parent_type and not return any * documents instead of an error. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/Joining/Nested.php b/src/DSL/Queries/Joining/Nested.php index 4849437..5493c0d 100644 --- a/src/DSL/Queries/Joining/Nested.php +++ b/src/DSL/Queries/Joining/Nested.php @@ -1,5 +1,7 @@ path($field); @@ -35,47 +37,47 @@ public static function create($field = null, $value = null) /** * Path to the nested object you wish to search. * - * @param string $path + * @param string $value * @return static */ - public function path($path) + public function path(string $value): static { - return $this->addProperty('path', $path); + return $this->addProperty('path', $value); } /** * Query you wish to run on nested objects in the path. * If an object matches the search, the nested query returns the root parent document. * - * @param mixed $query + * @param mixed $value * @return static */ - public function query($query) + public function query($value): static { - return $this->addProperty('query', Query::create($query)); + return $this->addProperty('query', Query::create($value)); } /** * Indicates how scores for matching child objects affect the root parent * document's relevance score. Valid values: avg (default), max, min, none, sum. * - * @param string $scoreMode + * @param string $value * @return static */ - public function scoreMode($scoreMode) + public function scoreMode(string $value): static { - return $this->addProperty('score_mode', $scoreMode); + return $this->addProperty('score_mode', $value); } /** * Indicates whether to ignore an unmapped path and not return any * documents instead of an error. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/Joining/ParentId.php b/src/DSL/Queries/Joining/ParentId.php index 6be7e8a..9914e16 100644 --- a/src/DSL/Queries/Joining/ParentId.php +++ b/src/DSL/Queries/Joining/ParentId.php @@ -1,5 +1,7 @@ addProperty('type', $type); + return $this->addProperty('type', $value); } /** * ID of the parent document. The query will return child documents of this parent document. * - * @param string $id + * @param string $value * @return static */ - public function id($id) + public function id(string $value): static { - return $this->addProperty('id', $id); + return $this->addProperty('id', $value); } /** * Indicates whether to ignore an unmapped type and not return any * documents instead of an error. Defaults to false. * - * @param bool $ignoreUnmapped + * @param bool $value * @return static */ - public function ignoreUnmapped($ignoreUnmapped) + public function ignoreUnmapped(bool $value): static { - return $this->addProperty('ignore_unmapped', $ignoreUnmapped); + return $this->addProperty('ignore_unmapped', $value); } } diff --git a/src/DSL/Queries/MatchAll.php b/src/DSL/Queries/MatchAll.php index efb892a..4f0af23 100644 --- a/src/DSL/Queries/MatchAll.php +++ b/src/DSL/Queries/MatchAll.php @@ -1,5 +1,7 @@ addQuery(QMatchAll::create($matchAll)); + return $this->addQuery(QMatchAll::create($value)); } /** @@ -26,7 +28,7 @@ public function matchAll($matchAll = null) * * @return $this */ - public function matchNone() + public function matchNone(): static { return $this->addQuery(MatchNone::create()); } diff --git a/src/DSL/Queries/MatchAll/MatchAll.php b/src/DSL/Queries/MatchAll/MatchAll.php index f461f26..1044cbf 100644 --- a/src/DSL/Queries/MatchAll/MatchAll.php +++ b/src/DSL/Queries/MatchAll/MatchAll.php @@ -1,5 +1,7 @@ addProperty('id', $id); + return $this->addProperty('id', $value); } /** - * (Optional) The script language. Defaults to painless. + * The script language. Defaults to painless. * - * @param string $lang + * @param string $value * @return static */ - public function lang($lang) + public function lang(string $value): static { - return $this->addProperty('lang', $lang); + return $this->addProperty('lang', $value); } /** - * (Required) The inline script source to execute. + * The inline script source to execute. * - * @param string $source + * @param string $value * @return static */ - public function source($source) + public function source(string $value): static { - return $this->addProperty('source', $source); + return $this->addProperty('source', $value); } /** - * (Optional) Named parameters passed into the script. + * Named parameters passed into the script. * - * @param array $params + * @param array $value * @return static */ - public function params($params) + public function params(array $value): static { - return $this->addProperty('params', $params); + return $this->addProperty('params', $value); } } diff --git a/src/DSL/Queries/Shape.php b/src/DSL/Queries/Shape.php index 1980745..ee708b8 100644 --- a/src/DSL/Queries/Shape.php +++ b/src/DSL/Queries/Shape.php @@ -1,5 +1,7 @@ addQuery(QShape::create($field, $value)); } diff --git a/src/DSL/Queries/Shape/Shape.php b/src/DSL/Queries/Shape/Shape.php index c92f0ee..b1de24e 100644 --- a/src/DSL/Queries/Shape/Shape.php +++ b/src/DSL/Queries/Shape/Shape.php @@ -1,5 +1,7 @@ addProperty('shape', $shape); + return $this->addProperty('shape', $value); } /** * Spatial relation operator to use at search time. * Valid values: INTERSECTS (default), DISJOINT, WITHIN, CONTAINS. * - * @param string $relation + * @param string $value * @return static */ - public function relation($relation) + public function relation(string $value): static { - return $this->addProperty('relation', $relation); + return $this->addProperty('relation', $value); } /** * Reference to a pre-indexed shape. Contains id, index, path, and routing fields * to identify the shape document in another index. * - * @param mixed $indexedShape + * @param mixed $value * @return static */ - public function indexedShape($indexedShape) + public function indexedShape($value): static { - return $this->addProperty('indexed_shape', $indexedShape); + return $this->addProperty('indexed_shape', $value); } } diff --git a/src/DSL/Queries/Span.php b/src/DSL/Queries/Span.php index 22bffbc..d20e19a 100644 --- a/src/DSL/Queries/Span.php +++ b/src/DSL/Queries/Span.php @@ -1,5 +1,7 @@ addQuery(SpanContaining::create($spanContaining)); + return $this->addQuery(SpanContaining::create($value)); } /** * Add a span_field_masking query. * - * @param mixed $spanFieldMasking + * @param mixed $value * @return $this */ - public function spanFieldMasking($spanFieldMasking) + public function spanFieldMasking($value): static { - return $this->addQuery(SpanFieldMasking::create($spanFieldMasking)); + return $this->addQuery(SpanFieldMasking::create($value)); } /** * Add a span_first query. * - * @param mixed $spanFirst + * @param mixed $value * @return $this */ - public function spanFirst($spanFirst) + public function spanFirst($value): static { - return $this->addQuery(SpanFirst::create($spanFirst)); + return $this->addQuery(SpanFirst::create($value)); } /** * Add a span_multi query. * - * @param mixed $spanMulti + * @param mixed $value * @return $this */ - public function spanMulti($spanMulti) + public function spanMulti($value): static { - return $this->addQuery(SpanMulti::create($spanMulti)); + return $this->addQuery(SpanMulti::create($value)); } /** * Add a span_near query. * - * @param mixed $spanNear + * @param mixed $value * @return $this */ - public function spanNear($spanNear) + public function spanNear($value): static { - return $this->addQuery(SpanNear::create($spanNear)); + return $this->addQuery(SpanNear::create($value)); } /** * Add a span_not query. * - * @param mixed $spanNot + * @param mixed $value * @return $this */ - public function spanNot($spanNot) + public function spanNot($value): static { - return $this->addQuery(SpanNot::create($spanNot)); + return $this->addQuery(SpanNot::create($value)); } /** * Add a span_or query. * - * @param mixed $spanOr + * @param mixed $value * @return $this */ - public function spanOr($spanOr) + public function spanOr($value): static { - return $this->addQuery(SpanOr::create($spanOr)); + return $this->addQuery(SpanOr::create($value)); } /** @@ -102,7 +104,7 @@ public function spanOr($spanOr) * @param mixed $value * @return $this */ - public function spanTerm($field, $value = null) + public function spanTerm($field, $value = null): static { return $this->addQuery(SpanTerm::create($field, $value)); } @@ -110,11 +112,11 @@ public function spanTerm($field, $value = null) /** * Add a span_within query. * - * @param mixed $spanWithin + * @param mixed $value * @return $this */ - public function spanWithin($spanWithin) + public function spanWithin($value): static { - return $this->addQuery(SpanWithin::create($spanWithin)); + return $this->addQuery(SpanWithin::create($value)); } } diff --git a/src/DSL/Queries/Span/SpanContaining.php b/src/DSL/Queries/Span/SpanContaining.php index b44f222..d2593fa 100644 --- a/src/DSL/Queries/Span/SpanContaining.php +++ b/src/DSL/Queries/Span/SpanContaining.php @@ -1,5 +1,7 @@ addProperty('little', Query::create($little)); + return $this->addProperty('little', Query::create($value)); } /** * The big span query that must contain matches from the little span. * - * @param mixed $big + * @param mixed $value * @return static */ - public function big($big) + public function big($value): static { - return $this->addProperty('big', Query::create($big)); + return $this->addProperty('big', Query::create($value)); } } diff --git a/src/DSL/Queries/Span/SpanFieldMasking.php b/src/DSL/Queries/Span/SpanFieldMasking.php index ea62edf..458344d 100644 --- a/src/DSL/Queries/Span/SpanFieldMasking.php +++ b/src/DSL/Queries/Span/SpanFieldMasking.php @@ -1,5 +1,7 @@ addProperty('query', Query::create($query)); - } - - /** - * The masked field to use for the span query. - * - * @param string $field + * @param mixed $value * @return static */ - public function field($field) + public function query($value): static { - return $this->addProperty('field', $field); + return $this->addProperty('query', Query::create($value)); } } diff --git a/src/DSL/Queries/Span/SpanFirst.php b/src/DSL/Queries/Span/SpanFirst.php index 2881f43..660b048 100644 --- a/src/DSL/Queries/Span/SpanFirst.php +++ b/src/DSL/Queries/Span/SpanFirst.php @@ -1,5 +1,7 @@ addProperty('match', Query::create($match)); + return $this->addProperty('match', Query::create($value)); } /** * The maximum end position permitted for the span match. * - * @param int $end + * @param int $value * @return static */ - public function end($end) + public function end(int $value): static { - return $this->addProperty('end', $end); + return $this->addProperty('end', $value); } } diff --git a/src/DSL/Queries/Span/SpanMulti.php b/src/DSL/Queries/Span/SpanMulti.php index ecdad4a..61094f9 100644 --- a/src/DSL/Queries/Span/SpanMulti.php +++ b/src/DSL/Queries/Span/SpanMulti.php @@ -1,5 +1,7 @@ addProperty('match', Query::create($match)); + return $this->addProperty('match', Query::create($value)); } } diff --git a/src/DSL/Queries/Span/SpanNear.php b/src/DSL/Queries/Span/SpanNear.php index c5a5ce6..3cf7404 100644 --- a/src/DSL/Queries/Span/SpanNear.php +++ b/src/DSL/Queries/Span/SpanNear.php @@ -1,10 +1,11 @@ addProperty('clauses', Query::create($clauses)->multi(true)); - } - - /** - * Append a span query clause. Supports multiple calls to incrementally build. - * - * @param mixed $clause + * @param mixed $value * @return static */ - public function addClause($clause) + public function clauses($value): static { - return $this->pushClause('clauses', $clause); + return $this->addClause('clauses', $value); } /** * The maximum number of positions allowed between matching spans. * - * @param int $slop + * @param int $value * @return static */ - public function slop($slop) + public function slop(int $value): static { - return $this->addProperty('slop', $slop); + return $this->addProperty('slop', $value); } /** * Whether the span clauses must appear in their specified order. * - * @param bool $inOrder + * @param bool $value * @return static */ - public function inOrder($inOrder) + public function inOrder(bool $value): static { - return $this->addProperty('in_order', $inOrder); + return $this->addProperty('in_order', $value); } } diff --git a/src/DSL/Queries/Span/SpanNot.php b/src/DSL/Queries/Span/SpanNot.php index 31b77c3..9a7d34f 100644 --- a/src/DSL/Queries/Span/SpanNot.php +++ b/src/DSL/Queries/Span/SpanNot.php @@ -1,5 +1,7 @@ addProperty('include', Query::create($include)); + return $this->addProperty('include', Query::create($value)); } /** * The span query whose overlapping matches are excluded. * - * @param mixed $exclude + * @param mixed $value * @return static */ - public function exclude($exclude) + public function exclude($value): static { - return $this->addProperty('exclude', Query::create($exclude)); + return $this->addProperty('exclude', Query::create($value)); } /** * The number of positions before the include span that must not overlap with the exclude span. * - * @param int $pre + * @param int $value * @return static */ - public function pre($pre) + public function pre(int $value): static { - return $this->addProperty('pre', $pre); + return $this->addProperty('pre', $value); } /** * The number of positions after the include span that must not overlap with the exclude span. * - * @param int $post + * @param int $value * @return static */ - public function post($post) + public function post(int $value): static { - return $this->addProperty('post', $post); + return $this->addProperty('post', $value); } /** * The number of positions both before and after the include span that must not overlap with the exclude span. * - * @param int $dist + * @param int $value * @return static */ - public function dist($dist) + public function dist(int $value): static { - return $this->addProperty('dist', $dist); + return $this->addProperty('dist', $value); } } diff --git a/src/DSL/Queries/Span/SpanOr.php b/src/DSL/Queries/Span/SpanOr.php index df9a3c9..1b7152d 100644 --- a/src/DSL/Queries/Span/SpanOr.php +++ b/src/DSL/Queries/Span/SpanOr.php @@ -1,10 +1,11 @@ addProperty('clauses', Query::create($clauses)->multi(true)); - } - - /** - * Append a span query clause. Supports multiple calls to incrementally build. - * - * @param mixed $clause + * @param mixed $value * @return static */ - public function addClause($clause) + public function clauses($value): static { - return $this->pushClause('clauses', $clause); + return $this->addClause('clauses', $value); } } diff --git a/src/DSL/Queries/Span/SpanTerm.php b/src/DSL/Queries/Span/SpanTerm.php index ad34f62..1a468e8 100644 --- a/src/DSL/Queries/Span/SpanTerm.php +++ b/src/DSL/Queries/Span/SpanTerm.php @@ -1,5 +1,7 @@ addProperty('term', $term); + return $this->addProperty('term', $value); } /** * The value of the term to match (alias for the field value). * - * @param string $value + * @param string|int|float|bool $value * @return static */ - public function value($value) + public function value(string|int|float|bool $value): static { return $this->addProperty('value', $value); } diff --git a/src/DSL/Queries/Span/SpanWithin.php b/src/DSL/Queries/Span/SpanWithin.php index d60ca32..2e63664 100644 --- a/src/DSL/Queries/Span/SpanWithin.php +++ b/src/DSL/Queries/Span/SpanWithin.php @@ -1,5 +1,7 @@ addProperty('little', Query::create($little)); + return $this->addProperty('little', Query::create($value)); } /** * The big span query that must contain matches from the little span. * - * @param mixed $big + * @param mixed $value * @return static */ - public function big($big) + public function big($value): static { - return $this->addProperty('big', Query::create($big)); + return $this->addProperty('big', Query::create($value)); } } diff --git a/src/DSL/Queries/Specialized.php b/src/DSL/Queries/Specialized.php index 445d49c..0bdcb10 100644 --- a/src/DSL/Queries/Specialized.php +++ b/src/DSL/Queries/Specialized.php @@ -1,5 +1,7 @@ addQuery(DistanceFeature::create($distanceFeature)); + return $this->addQuery(DistanceFeature::create($value)); } /** * Add a more_like_this query. * - * @param mixed $moreLikeThis + * @param mixed $value * @return $this */ - public function moreLikeThis($moreLikeThis) + public function moreLikeThis($value): static { - return $this->addQuery(MoreLikeThis::create($moreLikeThis)); + return $this->addQuery(MoreLikeThis::create($value)); } /** * Add a percolate query. * - * @param mixed $percolate + * @param mixed $value * @return $this */ - public function percolate($percolate) + public function percolate($value): static { - return $this->addQuery(Percolate::create($percolate)); + return $this->addQuery(Percolate::create($value)); } /** * Add a rank_feature query. * - * @param mixed $rankFeature + * @param mixed $value * @return $this */ - public function rankFeature($rankFeature) + public function rankFeature($value): static { - return $this->addQuery(RankFeature::create($rankFeature)); + return $this->addQuery(RankFeature::create($value)); } /** * Add a script query. * - * @param mixed $script + * @param mixed $value * @return $this */ - public function script($script) + public function script($value): static { - return $this->addQuery(Script::create($script)); + return $this->addQuery(Script::create($value)); } /** * Add a script_score query. * - * @param mixed $scriptScore + * @param mixed $value * @return $this */ - public function scriptScore($scriptScore) + public function scriptScore($value): static { - return $this->addQuery(ScriptScore::create($scriptScore)); + return $this->addQuery(ScriptScore::create($value)); } /** * Add a wrapper query. * - * @param mixed $wrapper + * @param mixed $value * @return $this */ - public function wrapper($wrapper) + public function wrapper($value): static { - return $this->addQuery(Wrapper::create($wrapper)); + return $this->addQuery(Wrapper::create($value)); } /** * Add a pinned query. * - * @param mixed $pinned + * @param mixed $value * @return $this */ - public function pinned($pinned) + public function pinned($value): static { - return $this->addQuery(Pinned::create($pinned)); + return $this->addQuery(Pinned::create($value)); } } diff --git a/src/DSL/Queries/Specialized/DistanceFeature.php b/src/DSL/Queries/Specialized/DistanceFeature.php index f1d2bc5..7bd8715 100644 --- a/src/DSL/Queries/Specialized/DistanceFeature.php +++ b/src/DSL/Queries/Specialized/DistanceFeature.php @@ -1,5 +1,7 @@ addProperty('origin', $origin); + return $this->addProperty('origin', $value); } /** * Distance from the origin at which relevance scores receive half of the boost value. * - * @param mixed $pivot + * @param string $value * @return static */ - public function pivot($pivot) + public function pivot(string $value): static { - return $this->addProperty('pivot', $pivot); + return $this->addProperty('pivot', $value); } } diff --git a/src/DSL/Queries/Specialized/MoreLikeThis.php b/src/DSL/Queries/Specialized/MoreLikeThis.php index d7ab65b..f12b310 100644 --- a/src/DSL/Queries/Specialized/MoreLikeThis.php +++ b/src/DSL/Queries/Specialized/MoreLikeThis.php @@ -1,5 +1,7 @@ $array + * @param array $value * @return static */ - public function fields($array) + public function fields(array $value): static { - return $this->addProperty('fields', $array); + return $this->addProperty('fields', $value); } /** * Text or documents to find similar documents for. * - * @param mixed $string + * @param mixed $value * @return static */ - public function like($string) + public function like($value): static { - return $this->addProperty('like', $string); + return $this->addProperty('like', $value); } /** * Minimum term frequency below which terms are ignored. Defaults to 2. * - * @param int $int + * @param int $value * @return static */ - public function minTermFreq($int) + public function minTermFreq(int $value): static { - return $this->addProperty('min_term_freq', $int); + return $this->addProperty('min_term_freq', $value); } /** * Maximum number of query terms to be selected per result document. Defaults to 25. * - * @param int $int + * @param int $value * @return static */ - public function maxQueryTerms($int) + public function maxQueryTerms(int $value): static { - return $this->addProperty('max_query_terms', $int); + return $this->addProperty('max_query_terms', $value); } } diff --git a/src/DSL/Queries/Specialized/Percolate.php b/src/DSL/Queries/Specialized/Percolate.php index 74d882e..635d058 100644 --- a/src/DSL/Queries/Specialized/Percolate.php +++ b/src/DSL/Queries/Specialized/Percolate.php @@ -1,5 +1,7 @@ addProperty('document', $document); + return $this->addProperty('document', $value); } } diff --git a/src/DSL/Queries/Specialized/Pinned.php b/src/DSL/Queries/Specialized/Pinned.php index 0ed91d5..81c11a9 100644 --- a/src/DSL/Queries/Specialized/Pinned.php +++ b/src/DSL/Queries/Specialized/Pinned.php @@ -1,5 +1,7 @@ $ids + * @param array $value * @return static */ - public function ids($ids) + public function ids(array $value): static { - return $this->addProperty('ids', $ids); + return $this->addProperty('ids', $value); } /** * The organic query used to rank non-pinned documents. * - * @param mixed $organic + * @param mixed $value * @return static */ - public function organic($organic) + public function organic($value): static { - return $this->addProperty('organic', Query::create($organic)); + return $this->addProperty('organic', Query::create($value)); } /** * A document to pin instead of using an ID. * - * @param mixed $doc + * @param mixed $value * @return static */ - public function doc($doc) + public function doc($value): static { - return $this->addProperty('doc', $doc); + return $this->addProperty('doc', $value); } } diff --git a/src/DSL/Queries/Specialized/RankFeature.php b/src/DSL/Queries/Specialized/RankFeature.php index 0cd8746..ed77d5b 100644 --- a/src/DSL/Queries/Specialized/RankFeature.php +++ b/src/DSL/Queries/Specialized/RankFeature.php @@ -1,5 +1,7 @@ addProperty('saturation', $saturation); + return $this->addProperty('saturation', $value); } /** * Logarithmic function to compute the score. Supports a scaling_factor parameter. * - * @param mixed $log + * @param mixed $value * @return static */ - public function log($log) + public function log($value): static { - return $this->addProperty('log', $log); + return $this->addProperty('log', $value); } /** * Sigmoid function to compute the score. Requires exponent and pivot parameters. * - * @param mixed $sigmoid + * @param mixed $value * @return static */ - public function sigmoid($sigmoid) + public function sigmoid($value): static { - return $this->addProperty('sigmoid', $sigmoid); + return $this->addProperty('sigmoid', $value); } /** * Linear function to compute the score, producing a linear relation between the feature value and the score. * - * @param mixed $linear + * @param mixed $value * @return static */ - public function linear($linear) + public function linear($value): static { - return $this->addProperty('linear', $linear); + return $this->addProperty('linear', $value); } } diff --git a/src/DSL/Queries/Specialized/Script.php b/src/DSL/Queries/Specialized/Script.php index a51cd1e..2c5cb2e 100644 --- a/src/DSL/Queries/Specialized/Script.php +++ b/src/DSL/Queries/Specialized/Script.php @@ -1,5 +1,7 @@ addProperty('script', \ElasticKit\DSL\Queries\Script::create($script)); + return $this->addProperty('script', \ElasticKit\DSL\Queries\Script::create($value)); } } diff --git a/src/DSL/Queries/Specialized/ScriptScore.php b/src/DSL/Queries/Specialized/ScriptScore.php index d58091b..bcee851 100644 --- a/src/DSL/Queries/Specialized/ScriptScore.php +++ b/src/DSL/Queries/Specialized/ScriptScore.php @@ -1,5 +1,7 @@ addProperty('query', Query::create($query)); + return $this->addProperty('query', Query::create($value)); } /** * The script used to compute the new relevance score. * - * @param mixed $script + * @param mixed $value * @return static */ - public function script($script) + public function script($value): static { - return $this->addProperty('script', \ElasticKit\DSL\Queries\Script::create($script)); + return $this->addProperty('script', \ElasticKit\DSL\Queries\Script::create($value)); } /** * Minimum relevance score threshold. Documents with a lower score are excluded. * - * @param float $minScore + * @param float $value * @return static */ - public function minScore($minScore) + public function minScore(float $value): static { - return $this->addProperty('min_score', Query::create($minScore)); + return $this->addProperty('min_score', Query::create($value)); } } diff --git a/src/DSL/Queries/Specialized/Wrapper.php b/src/DSL/Queries/Specialized/Wrapper.php index 53f8b4d..48c83c7 100644 --- a/src/DSL/Queries/Specialized/Wrapper.php +++ b/src/DSL/Queries/Specialized/Wrapper.php @@ -1,5 +1,7 @@ query($field); @@ -32,11 +34,11 @@ public static function create($field = null, $value = null) /** * A query in base64 encoded format. * - * @param string $query + * @param string $value * @return static */ - public function query($query) + public function query(string $value): static { - return $this->addProperty('query', $query); + return $this->addProperty('query', $value); } } diff --git a/src/DSL/Queries/TermLevel.php b/src/DSL/Queries/TermLevel.php index 7e06f7a..6f1e34c 100644 --- a/src/DSL/Queries/TermLevel.php +++ b/src/DSL/Queries/TermLevel.php @@ -1,5 +1,7 @@ addQuery(Fuzzy::create($field, $value)); } @@ -50,7 +52,7 @@ public function fuzzy($field, $value = null) * @param mixed $field * @return $this */ - public function exists($field) + public function exists($field): static { return $this->addQuery(Exists::create($field)); } @@ -58,15 +60,15 @@ public function exists($field) /** * Returns documents based on their IDs. This query uses document IDs stored in the _id field. * - * @param mixed $ids + * @param mixed $value * @return $this */ - public function ids($ids) + public function ids($value): static { - if (is_array($ids) && !isset($ids['values'])) { - $ids = ['values' => $ids]; + if (is_array($value) && !isset($value['values'])) { + $value = ['values' => $value]; } - return $this->addQuery(IDs::create($ids)); + return $this->addQuery(IDs::create($value)); } /** @@ -76,7 +78,7 @@ public function ids($ids) * @param mixed $value * @return $this */ - public function prefix($field, $value = null) + public function prefix($field, $value = null): static { return $this->addQuery(Prefix::create($field, $value)); } @@ -91,7 +93,7 @@ public function prefix($field, $value = null) * @param callable|array $value * @return $this */ - public function range($field, $value = null) + public function range($field, $value = null): static { return $this->addQuery(Range::create($field, $value)); } @@ -103,7 +105,7 @@ public function range($field, $value = null) * @param mixed $value * @return $this */ - public function regexp($field, $value = null) + public function regexp($field, $value = null): static { return $this->addQuery(Regexp::create($field, $value)); } @@ -117,7 +119,7 @@ public function regexp($field, $value = null) * @param callable|string|array $value * @return $this */ - public function term($field, $value = null) + public function term($field, $value = null): static { return $this->addQuery(Term::create($field, $value)); } @@ -132,7 +134,7 @@ public function term($field, $value = null) * @param mixed $value * @return $this */ - public function terms($field, $value = null) + public function terms($field, $value = null): static { return $this->addQuery(Terms::create($field, $value)); } @@ -149,7 +151,7 @@ public function terms($field, $value = null) * @param mixed $value * @return $this */ - public function termsSet($field, $value = null) + public function termsSet($field, $value = null): static { return $this->addQuery(TermsSet::create($field, $value)); } @@ -163,7 +165,7 @@ public function termsSet($field, $value = null) * @param mixed $value * @return $this */ - public function wildcard($field, $value = null) + public function wildcard($field, $value = null): static { return $this->addQuery(Wildcard::create($field, $value)); } diff --git a/src/DSL/Queries/TermLevel/Exists.php b/src/DSL/Queries/TermLevel/Exists.php index 7d82f6d..850d5c7 100644 --- a/src/DSL/Queries/TermLevel/Exists.php +++ b/src/DSL/Queries/TermLevel/Exists.php @@ -1,5 +1,7 @@ . @@ -16,7 +18,7 @@ class Fuzzy extends Node * @param string $value * @return static */ - public function value($value) + public function value(string $value): static { return $this->addProperty('value', $value); } @@ -24,55 +26,55 @@ public function value($value) /** * Maximum edit distance allowed for matching. See Fuzziness for valid values and more information. * - * @param string $fuzziness + * @param int|string $value * @return static */ - public function fuzziness($fuzziness) + public function fuzziness(int|string $value): static { - return $this->addProperty('fuzziness', $fuzziness); + return $this->addProperty('fuzziness', $value); } /** * Maximum number of variations created. Defaults to 50. * - * @param int $maxExpansions + * @param int $value * @return static */ - public function maxExpansions($maxExpansions) + public function maxExpansions(int $value): static { - return $this->addProperty('max_expansions', $maxExpansions); + return $this->addProperty('max_expansions', $value); } /** * Number of beginning characters left unchanged when creating expansions. Defaults to 0. * - * @param int $prefixLength + * @param int $value * @return static */ - public function prefixLength($prefixLength) + public function prefixLength(int $value): static { - return $this->addProperty('prefix_length', $prefixLength); + return $this->addProperty('prefix_length', $value); } /** * Indicates whether edits include transpositions of two adjacent characters (ab → ba). Defaults to true. * - * @param bool $transpositions + * @param bool $value * @return static */ - public function transpositions($transpositions) + public function transpositions(bool $value): static { - return $this->addProperty('transpositions', $transpositions); + return $this->addProperty('transpositions', $value); } /** * Method used to rewrite the query. For valid values and more information, see the rewrite parameter. * - * @param string $rewrite + * @param string $value * @return static */ - public function rewrite($rewrite) + public function rewrite(string $value): static { - return $this->addProperty('rewrite', $rewrite); + return $this->addProperty('rewrite', $value); } } diff --git a/src/DSL/Queries/TermLevel/IDs.php b/src/DSL/Queries/TermLevel/IDs.php index 2f36123..fcab3a4 100644 --- a/src/DSL/Queries/TermLevel/IDs.php +++ b/src/DSL/Queries/TermLevel/IDs.php @@ -1,5 +1,7 @@ $values + * @param array $value * @return static */ - public function values($values) + public function values(array $value): static { - return $this->addProperty('values', $values); + return $this->addProperty('values', $value); } } diff --git a/src/DSL/Queries/TermLevel/Prefix.php b/src/DSL/Queries/TermLevel/Prefix.php index 609dc7b..eec7485 100644 --- a/src/DSL/Queries/TermLevel/Prefix.php +++ b/src/DSL/Queries/TermLevel/Prefix.php @@ -1,14 +1,16 @@ . @@ -16,7 +18,7 @@ class Prefix extends Node * @param string $value * @return static */ - public function value($value) + public function value(string $value): static { return $this->addProperty('value', $value); } @@ -24,22 +26,22 @@ public function value($value) /** * Method used to rewrite the query. For valid values and more information, see the rewrite parameter. * - * @param string $rewrite + * @param string $value * @return static */ - public function rewrite($rewrite) + public function rewrite(string $value): static { - return $this->addProperty('rewrite', $rewrite); + return $this->addProperty('rewrite', $value); } /** * Allows ASCII case insensitive matching of the value with the indexed field values when set to true. Default is false which means the case sensitivity of matching depends on the underlying field’s mapping. * - * @param bool $caseInsensitive + * @param bool $value * @return static */ - public function caseInsensitive($caseInsensitive) + public function caseInsensitive(bool $value): static { - return $this->addProperty('case_insensitive', $caseInsensitive); + return $this->addProperty('case_insensitive', $value); } } diff --git a/src/DSL/Queries/TermLevel/Range.php b/src/DSL/Queries/TermLevel/Range.php index 892e331..dc2192f 100644 --- a/src/DSL/Queries/TermLevel/Range.php +++ b/src/DSL/Queries/TermLevel/Range.php @@ -1,60 +1,62 @@ addProperty('gte', $gte); + return $this->addProperty('gte', $value); } /** - * (Optional) Greater than. + * Greater than. * - * @param mixed $gt + * @param string|int|float|bool $value * @return static */ - public function gt($gt) + public function gt(string|int|float|bool $value): static { - return $this->addProperty('gt', $gt); + return $this->addProperty('gt', $value); } /** - * (Optional) Less than or equal to. + * Less than or equal to. * - * @param mixed $lte + * @param string|int|float|bool $value * @return static */ - public function lte($lte) + public function lte(string|int|float|bool $value): static { - return $this->addProperty('lte', $lte); + return $this->addProperty('lte', $value); } /** - * (Optional) Less than. + * Less than. * - * @param mixed $lt + * @param string|int|float|bool $value * @return static */ - public function lt($lt) + public function lt(string|int|float|bool $value): static { - return $this->addProperty('lt', $lt); + return $this->addProperty('lt', $value); } /** @@ -66,12 +68,12 @@ public function lt($lt) * * If a format or date value is incomplete, the range query replaces any missing components with default values. See Missing date components. * - * @param string $format + * @param string $value * @return static */ - public function format($format) + public function format(string $value): static { - return $this->addProperty('format', $format); + return $this->addProperty('format', $value); } /** @@ -84,22 +86,22 @@ public function format($format) * WITHIN * Matches documents with a range field value entirely within the query’s range. * - * @param string $relation + * @param string $value * @return static */ - public function relation($relation) + public function relation(string $value): static { - return $this->addProperty('relation', $relation); + return $this->addProperty('relation', $value); } /** * Coordinated Universal Time (UTC) offset or IANA time zone used to convert date values in the query to UTC. * - * @param string $timeZone + * @param string $value * @return static */ - public function timeZone($timeZone) + public function timeZone(string $value): static { - return $this->addProperty('time_zone', $timeZone); + return $this->addProperty('time_zone', $value); } } diff --git a/src/DSL/Queries/TermLevel/Regexp.php b/src/DSL/Queries/TermLevel/Regexp.php index 80ed83c..f7d9a83 100644 --- a/src/DSL/Queries/TermLevel/Regexp.php +++ b/src/DSL/Queries/TermLevel/Regexp.php @@ -1,14 +1,16 @@ . For a list of supported operators, see Regular expression syntax. @@ -20,7 +22,7 @@ class Regexp extends Node * @param string $value * @return static */ - public function value($value) + public function value(string $value): static { return $this->addProperty('value', $value); } @@ -28,23 +30,23 @@ public function value($value) /** * Enables optional operators for the regular expression. For valid values and more information, see Regular expression syntax. * - * @param string $flags + * @param string $value * @return static */ - public function flags($flags) + public function flags(string $value): static { - return $this->addProperty('flags', $flags); + return $this->addProperty('flags', $value); } /** * Allows case insensitive matching of the regular expression value with the indexed field values when set to true. Default is false which means the case sensitivity of matching depends on the underlying field’s mapping. * - * @param bool $caseInsensitive + * @param bool $value * @return static */ - public function caseInsensitive($caseInsensitive) + public function caseInsensitive(bool $value): static { - return $this->addProperty('case_insensitive', $caseInsensitive); + return $this->addProperty('case_insensitive', $value); } /** @@ -54,22 +56,22 @@ public function caseInsensitive($caseInsensitive) * * You can use this parameter to prevent that conversion from unintentionally consuming too many resources. You may need to increase this limit to run complex regular expressions. * - * @param int $maxDeterminizedStates + * @param int $value * @return static */ - public function maxDeterminizedStates($maxDeterminizedStates) + public function maxDeterminizedStates(int $value): static { - return $this->addProperty('max_determinized_states', $maxDeterminizedStates); + return $this->addProperty('max_determinized_states', $value); } /** * Method used to rewrite the query. For valid values and more information, see the rewrite parameter. * - * @param string $rewrite + * @param string $value * @return static */ - public function rewrite($rewrite) + public function rewrite(string $value): static { - return $this->addProperty('rewrite', $rewrite); + return $this->addProperty('rewrite', $value); } } diff --git a/src/DSL/Queries/TermLevel/Term.php b/src/DSL/Queries/TermLevel/Term.php index 699d9cf..ad4f0e8 100644 --- a/src/DSL/Queries/TermLevel/Term.php +++ b/src/DSL/Queries/TermLevel/Term.php @@ -1,5 +1,7 @@ . To return a document, the term must exactly match the field value, including whitespace and capitalization. * - * @param string $value + * @param string|int|float|bool $value * @return static */ - public function value($value) + public function value(string|int|float|bool $value): static { return $this->addProperty('value', $value); } @@ -36,12 +38,12 @@ public function value($value) * * Allows ASCII case insensitive matching of the value with the indexed field values when set to true. Default is false which means the case sensitivity of matching depends on the underlying field’s mapping. * - * @param bool $caseInsensitive + * @param bool $value * @return static * @version 7.10.0 */ - public function caseInsensitive($caseInsensitive) + public function caseInsensitive(bool $value): static { - return $this->addProperty('case_insensitive', $caseInsensitive); + return $this->addProperty('case_insensitive', $value); } } diff --git a/src/DSL/Queries/TermLevel/Terms.php b/src/DSL/Queries/TermLevel/Terms.php index e980715..ab8cb59 100644 --- a/src/DSL/Queries/TermLevel/Terms.php +++ b/src/DSL/Queries/TermLevel/Terms.php @@ -1,14 +1,16 @@ $values + * @param array $values * @return static */ - public function values($field, $values) + public function values(string $field, array $values): static { return $this->addProperty($field, $values); } diff --git a/src/DSL/Queries/TermLevel/TermsSet.php b/src/DSL/Queries/TermLevel/TermsSet.php index e3a5669..1c60488 100644 --- a/src/DSL/Queries/TermLevel/TermsSet.php +++ b/src/DSL/Queries/TermLevel/TermsSet.php @@ -1,5 +1,7 @@ . To return a document, a required number of terms must exactly match the field values, including whitespace and capitalization. * * The required number of matching terms is defined in the minimum_should_match, minimum_should_match_field or minimum_should_match_script parameters. Exactly one of these parameters must be provided. * - * @param array $terms + * @param array $value * @return static */ - public function terms($terms) + public function terms(array $value): static { - return $this->addProperty('terms', $terms); + return $this->addProperty('terms', $value); } /** - * (Optional) Specification for the number of matching terms required to return a document. + * Specification for the number of matching terms required to return a document. * * For valid values, see minimum_should_match parameter. * - * @param mixed $minimumShouldMatch + * @param int|string $value * @return static */ - public function minimumShouldMatch($minimumShouldMatch) + public function minimumShouldMatch(int|string $value): static { - return $this->addProperty('minimum_should_match', $minimumShouldMatch); + return $this->addProperty('minimum_should_match', $value); } /** - * @param string $field + * @param string $value * @return static */ - public function minimumShouldMatchField($field) + public function minimumShouldMatchField(string $value): static { - return $this->addProperty('minimum_should_match_field', $field); + return $this->addProperty('minimum_should_match_field', $value); } /** @@ -56,11 +58,11 @@ public function minimumShouldMatchField($field) * * For an example query using the minimum_should_match_script parameter, see How to use the minimum_should_match_script parameter. * - * @param mixed $minimumShouldMatchScript + * @param mixed $value * @return static */ - public function minimumShouldMatchScript($minimumShouldMatchScript) + public function minimumShouldMatchScript($value): static { - return $this->addProperty('minimum_should_match_script', Script::create($minimumShouldMatchScript)); + return $this->addProperty('minimum_should_match_script', Script::create($value)); } } diff --git a/src/DSL/Queries/TermLevel/Wildcard.php b/src/DSL/Queries/TermLevel/Wildcard.php index aaaff69..88c6d69 100644 --- a/src/DSL/Queries/TermLevel/Wildcard.php +++ b/src/DSL/Queries/TermLevel/Wildcard.php @@ -1,36 +1,38 @@ addProperty('case_insensitive', $caseInsensitive); + return $this->addProperty('case_insensitive', $value); } /** * Method used to rewrite the query. For valid values and more information, see the rewrite parameter. * - * @param string $rewrite + * @param string $value * @return static */ - public function rewrite($rewrite) + public function rewrite(string $value): static { - return $this->addProperty('rewrite', $rewrite); + return $this->addProperty('rewrite', $value); } /** @@ -45,7 +47,7 @@ public function rewrite($rewrite) * @param string $value * @return static */ - public function value($value) + public function value(string $value): static { return $this->addProperty('value', $value); } @@ -53,11 +55,11 @@ public function value($value) /** * An alias for the value parameter. If you specify both value and wildcard, the query uses the last one in the request body. * - * @param string $wildcard + * @param string $value * @return static */ - public function wildcard($wildcard) + public function wildcard(string $value): static { - return $this->addProperty('wildcard', $wildcard); + return $this->addProperty('wildcard', $value); } } diff --git a/src/DSL/Query.php b/src/DSL/Query.php index 30acc37..24eaf55 100644 --- a/src/DSL/Query.php +++ b/src/DSL/Query.php @@ -1,5 +1,7 @@ */ - protected $_queryClauses = []; + protected array $_queries = []; /** * Aggregation nodes stored independently from type properties. * * @var array */ - protected $_aggregations = []; - - /** - * Set whether the query supports multiple clauses. - * - * @param bool $multi - * @return static - */ - protected function setMulti($multi) - { - $this->_multi = $multi; - return $this; - } - - /** - * Whether the query supports multiple clauses. - * - * @return bool - */ - protected function isMulti() - { - return $this->_multi; - } + protected array $_aggregations = []; /** * Initialize the query container. @@ -99,29 +80,50 @@ public function __construct($field = null, $value = null) return; } if ($field instanceof Closure) { - $field($this); + $this->fromClosure($field); } elseif ($field instanceof Node) { - $this->_queryClauses[] = $field; + $this->_queries[] = $field; } elseif (is_array($field)) { - if (array_key_exists('query', $field)) { - $this->_properties = $field; - } else { - $this->_queryClauses[] = $field; - } - } elseif ($field !== null) { + $this->fromArray($field); + } + } + + /** + * Initialize from a raw ES body array. + * + * Arrays with a 'query' key are stored as-is (raw DSL). + * Other arrays are added as query clauses. + * + * @param array $field + */ + protected function fromArray(array $field): void + { + if (array_key_exists('query', $field)) { $this->_properties = $field; + } else { + $this->_queries[] = $field; } } + /** + * Get all query clauses. + * + * @return array + */ + public function getQueries(): array + { + return $this->_queries; + } + /** * Add a query clause to the query container. * - * @param mixed $query + * @param mixed $clause * @return $this */ - public function addQuery($query) + public function addQuery($clause): static { - $this->_queryClauses[] = $query; + $this->_queries[] = $clause; return $this; } @@ -133,7 +135,7 @@ public function addQuery($query) * @param mixed $default * @return $this */ - public function when($condition, $query, $default = null) + public function when(bool|callable $condition, $query, $default = null): static { $truthy = is_callable($condition) ? $condition() : $condition; @@ -159,7 +161,7 @@ public function when($condition, $query, $default = null) * @return $this * @throws \BadMethodCallException if called with a string alias and no definition */ - public function aggs($alias, $aggs = null) + public function aggs($alias, $aggs = null): static { if ($aggs === null && !is_string($alias)) { $aggs = $alias; @@ -210,17 +212,17 @@ public function aggs($alias, $aggs = null) * * @return array */ - public function toArray() + public function toArray(): array { - $dsl = is_array($this->_properties) ? $this->resolveProperties($this->_properties) : []; + $dsl = $this->_properties !== null ? $this->resolveProperties($this->_properties) : []; $query = $this->buildQuery(); if (!empty($query)) { $dsl['query'] = $query; } - $this->buildAggs($dsl); - $this->buildParams($dsl); + $dsl = $this->buildAggs($dsl); + $dsl = $this->buildParams($dsl); return array_filter($dsl, function ($v) { return $v !== []; @@ -230,21 +232,29 @@ public function toArray() /** * Build the query clause array from stored query clauses. * - * @return array|object + * @return array|object */ - private function buildQuery() + private function buildQuery(): array|object { - if (empty($this->_queryClauses)) { + if (empty($this->_queries)) { return $this->_multi ? (object)[] : []; } - $clauses = []; - foreach ($this->_queryClauses as $query) { - if ($query instanceof Query) { - foreach ($query->toArray()['query'] as $field => $item) { - $clauses[] = [$field => $item]; + // Flatten nested Query instances + $flat = []; + foreach ($this->_queries as $item) { + if ($item instanceof self) { + foreach ($item->getQueries() as $clause) { + $flat[] = $clause; } - } elseif ($query instanceof Node) { + } else { + $flat[] = $item; + } + } + + $clauses = []; + foreach ($flat as $query) { + if ($query instanceof Node) { $clauses[] = [$query->key() => $query->toArray()]; } elseif (is_array($query)) { foreach ($query as $field => $item) { @@ -257,7 +267,7 @@ private function buildQuery() } if ($this->_multi) { - return $clauses; // @phpstan-ignore return.type + return $clauses; } if (empty($clauses)) { return []; @@ -269,26 +279,27 @@ private function buildQuery() * Build aggregation entries into the DSL array. * * @param array $dsl - * @return void + * @return array */ - private function buildAggs(array &$dsl) + private function buildAggs(array $dsl): array { if (empty($this->_aggregations)) { - return; + return $dsl; } $dsl['aggs'] = []; foreach ($this->_aggregations as $agg) { $dsl['aggs'] += $agg->toArray(); } + return $dsl; } /** * Build search request parameters into the DSL array. * * @param array $dsl - * @return void + * @return array */ - private function buildParams(array &$dsl) + private function buildParams(array $dsl): array { foreach ($this->_params as $key => $value) { if ($value instanceof Query) { @@ -300,5 +311,6 @@ private function buildParams(array &$dsl) } $dsl[$key] = $value; } + return $dsl; } } diff --git a/src/DSL/Shared/ClausesSupport.php b/src/DSL/Shared/ClausesSupport.php deleted file mode 100644 index d543853..0000000 --- a/src/DSL/Shared/ClausesSupport.php +++ /dev/null @@ -1,69 +0,0 @@ -> - */ - protected $_clauses = []; - - /** - * Push a clause for the given property. - * - * @param string $key Property name (e.g. 'must', 'clauses', 'queries') - * @param mixed $clause - * @return static - */ - protected function pushClause(string $key, $clause) - { - $this->_clauses[$key][] = $clause; - return $this; - } - - /** - * Serialize to array, merging clauses into the final output. - * - * @return array - */ - public function toArray() - { - $this->mergeClauses(); - return parent::toArray(); - } - - /** - * Merge clauses into _properties, adding each clause - * directly to the target container's clause list. - * - * @return void - */ - private function mergeClauses() - { - foreach ($this->_clauses as $key => $clauses) { - if (!isset($this->_properties[$key])) { - $this->_properties[$key] = (new Query())->multi(true); - } - $target = $this->_properties[$key]; - if ($target instanceof Query) { - foreach ($clauses as $clause) { - $resolved = ($clause instanceof Closure) ? Query::create($clause) : $clause; - $target->addQuery($resolved); - } - } - } - $this->_clauses = []; - } -} diff --git a/src/DSL/Support/ClausesSupport.php b/src/DSL/Support/ClausesSupport.php new file mode 100644 index 0000000..7c28048 --- /dev/null +++ b/src/DSL/Support/ClausesSupport.php @@ -0,0 +1,38 @@ +_properties[$key])) { + $this->_properties[$key] = (new Query())->multi(true); + } + $target = $this->_properties[$key]; + if ($clause instanceof Closure) { + $clause($target); + } else { + $target->addQuery($clause); + } + return $this; + } +} diff --git a/src/DSL/Shared/RangeSupport.php b/src/DSL/Support/RangeSupport.php similarity index 89% rename from src/DSL/Shared/RangeSupport.php rename to src/DSL/Support/RangeSupport.php index f53cc05..6a35899 100644 --- a/src/DSL/Shared/RangeSupport.php +++ b/src/DSL/Support/RangeSupport.php @@ -1,6 +1,8 @@ =, >, <=, <) and [start, end] to ES range keys. @@ -27,7 +29,7 @@ public function __construct($field = null, $value = null) * @param array $props * @return array */ - private static function normalizeKeys(array $props) + private static function normalizeKeys(array $props): array { $operators = [ '>=' => 'gte', '>' => 'gt', '<=' => 'lte', '<' => 'lt', diff --git a/src/Index/AggregationShortcut.php b/src/Index/AggregationShortcut.php deleted file mode 100644 index 87581f2..0000000 --- a/src/Index/AggregationShortcut.php +++ /dev/null @@ -1,74 +0,0 @@ -aggregateScalar('max', $field); - } - - /** - * Return the minimum value of a field. - * - * @param string $field - * @return float|null - */ - public function min($field) - { - return $this->aggregateScalar('min', $field); - } - - /** - * Return the average value of a field. - * - * @param string $field - * @return float|null - */ - public function avg($field) - { - return $this->aggregateScalar('avg', $field); - } - - /** - * Return the sum of a field. - * - * @param string $field - * @return float|null - */ - public function sum($field) - { - return $this->aggregateScalar('sum', $field); - } - - /** - * Execute a metric aggregation and return the scalar value. - * - * @param string $type - * @param string $field - * @return float|null - */ - private function aggregateScalar($type, $field) - { - $saved = $this->query; - $this->query = clone $this->query; - $this->query->size(0); - $this->query->aggs('__scalar', [$type => ['field' => $field]]); - - $response = $this->doSearch($type); - - $this->query = $saved; - - return $response['aggregations']['__scalar']['value'] ?? null; - } -} diff --git a/src/Index/Bulk.php b/src/Index/Bulk.php index 2ab5c3b..f5c2ed3 100644 --- a/src/Index/Bulk.php +++ b/src/Index/Bulk.php @@ -1,50 +1,50 @@ */ - private $body = []; + private array $body = []; /** * @var int */ - private $retryOnConflict = 0; + private int $retryOnConflict = 0; /** * @var string|null */ - private $targetIndex = null; + private ?string $targetIndex = null; /** * @var int */ - private $batchSize = 0; + private int $batchSize = 0; /** * @var int */ - private $docCount = 0; + private int $docCount = 0; /** - * @param Index $index + * @var callable|null */ - public function __construct(Index $index) - { - $this->index = $index; + private $errorHandler = null; + + public function __construct( + private readonly Index $index + ) { } /** @@ -54,7 +54,7 @@ public function __construct(Index $index) * @return $this * @throws \InvalidArgumentException if indexName starts with a dot (system index) */ - public function target($indexName) + public function target(string $indexName): static { if (strpos($indexName, '.') === 0) { throw new InvalidArgumentException("System index names (starting with '.') are not allowed: {$indexName}"); @@ -71,20 +71,36 @@ public function target($indexName) * @param int $size * @return $this */ - public function batchSize($size) + public function batchSize(int $size): static { $this->batchSize = $size; return $this; } + /** + * Set a callback to handle bulk errors. + * + * The callback receives the raw ES response. To continue execution, simply + * return without throwing. To abort, throw an exception from the callback. + * Without an error handler, execute() throws RuntimeException on errors. + * + * @param callable $handler function (array $response): void + * @return $this + */ + public function onError(callable $handler): static + { + $this->errorHandler = $handler; + return $this; + } + /** * Set retry_on_conflict for all update actions in this batch. * * @param int $count * @return $this */ - public function retryOnConflict($count) + public function retryOnConflict(int $count): static { $this->retryOnConflict = $count; @@ -95,17 +111,17 @@ public function retryOnConflict($count) * Queue an index (create/overwrite) action. * * @param string|int|null $id document ID, or null to let ES auto-generate - * @param array $document + * @param array $data * @return $this */ - public function index($id, $document) + public function index(string|int|null $id, array $data): static { $action = ['index' => ['_index' => $this->resolveIndex()]]; if ($id !== null && $id !== '') { $action['index']['_id'] = $id; } $this->body[] = $action; - $this->body[] = $document; + $this->body[] = $data; $this->afterPush(); return $this; @@ -115,25 +131,25 @@ public function index($id, $document) * Alias for index(). Queue a save (create/overwrite) action. * * @param string|int|null $id - * @param array $document + * @param array $data * @return $this */ - public function save($id, $document) + public function save(string|int|null $id, array $data): static { - return $this->index($id, $document); + return $this->index($id, $data); } /** * Queue a create action (fail if document already exists). * * @param string|int $id - * @param array $document + * @param array $data * @return $this */ - public function create($id, $document) + public function create(string|int $id, array $data): static { $this->body[] = ['create' => ['_index' => $this->resolveIndex(), '_id' => $id]]; - $this->body[] = $document; + $this->body[] = $data; $this->afterPush(); return $this; @@ -149,7 +165,7 @@ public function create($id, $document) * @param bool $upsert * @return $this */ - public function update($id, $data, $upsert = false) + public function update(string|int $id, array $data, bool $upsert = false): static { $action = ['update' => ['_index' => $this->resolveIndex(), '_id' => $id]]; @@ -170,7 +186,7 @@ public function update($id, $data, $upsert = false) * @param string|int $id * @return $this */ - public function delete($id) + public function delete(string|int $id): static { $this->body[] = ['delete' => ['_index' => $this->resolveIndex(), '_id' => $id]]; $this->afterPush(); @@ -184,7 +200,7 @@ public function delete($id) * @param array $options top-level bulk API params (refresh, timeout, etc) * @return array */ - public function execute(array $options = []) + public function execute(array $options = []): array { if (empty($this->body)) { return []; @@ -195,7 +211,7 @@ public function execute(array $options = []) $e = new Event('bulk.execute.before', $indexName); $e->actions = $actions; - Index::dispatch($e); + EventDispatcher::dispatch($e); $start = microtime(true); $response = $this->index->getClient()->bulk( @@ -211,7 +227,24 @@ public function execute(array $options = []) $e->actions = $actions; $e->response = $response; $e->duration = $duration; - Index::dispatch($e); + EventDispatcher::dispatch($e); + + if (!empty($response['errors'])) { + if ($this->errorHandler) { + ($this->errorHandler)($response); + } else { + $json = json_encode($response, JSON_UNESCAPED_UNICODE); + // json_encode() can return false on malformed payloads; guard required + // under strict_types to avoid passing false to strlen(). + if ($json === false) { + $json = '(unable to encode bulk response)'; + } + if (strlen($json) > 4096) { + $json = substr($json, 0, 4096) . '... [truncated]'; + } + throw new RuntimeException("Bulk request has errors: {$json}"); + } + } return $response; } @@ -221,7 +254,7 @@ public function execute(array $options = []) * * @return string */ - private function resolveIndex() + private function resolveIndex(): string { return $this->targetIndex ?? $this->index->name(); } diff --git a/src/Index/ClientManager.php b/src/Index/ClientManager.php new file mode 100644 index 0000000..443ef51 --- /dev/null +++ b/src/Index/ClientManager.php @@ -0,0 +1,59 @@ + + */ + private static array $clients = []; + + /** + * Register an Elasticsearch client. Optionally name the connection. + * + * @param ClientInterface $client + * @param string $connection connection name, defaults to 'default' + * @return void + */ + public static function set(ClientInterface $client, string $connection = 'default'): void + { + self::$clients[$connection] = $client; + } + + /** + * Return the Elasticsearch client for the given connection name. + * + * @param string $connection + * @return ClientInterface + * @throws RuntimeException if the connection is not registered + */ + public static function get(string $connection = 'default'): ClientInterface + { + if (isset(self::$clients[$connection])) { + return self::$clients[$connection]; + } + throw new RuntimeException( + "Elasticsearch client not registered for connection '{$connection}'. " + . 'Call ClientManager::set($client) first.' + ); + } + + /** + * Reset all registered clients. Mainly for testing. + * + * @return void + */ + public static function reset(): void + { + self::$clients = []; + } +} diff --git a/src/Index/Doc.php b/src/Index/Doc.php index c5a194f..3ab8178 100644 --- a/src/Index/Doc.php +++ b/src/Index/Doc.php @@ -1,48 +1,44 @@ index = $index; - $this->id = $id; + public function __construct( + private readonly Index $index, + private readonly string|int|null $id + ) { } /** * Return the document ID. * - * @return string|int + * @return string|int|null */ - public function id() + public function id(): string|int|null { return $this->id; } @@ -53,7 +49,7 @@ public function id() * @param int $count * @return $this */ - public function retryOnConflict($count) + public function retryOnConflict(int $count): static { $this->retryOnConflict = $count; @@ -66,7 +62,7 @@ public function retryOnConflict($count) * @param string $value * @return $this */ - public function refresh($value) + public function refresh(string $value): static { $this->refresh = $value; @@ -78,11 +74,13 @@ public function refresh($value) * * @return array */ - public function get() + public function get(): array { + $id = $this->requireId('get'); + return $this->index->getClient()->get([ 'index' => $this->index->name(), - 'id' => $this->id, + 'id' => $id, ])->asArray(); } @@ -91,11 +89,13 @@ public function get() * * @return array */ - public function source() + public function source(): array { + $id = $this->requireId('source'); + return $this->index->getClient()->getSource([ 'index' => $this->index->name(), - 'id' => $this->id, + 'id' => $id, ])->asArray(); } @@ -104,11 +104,13 @@ public function source() * * @return bool */ - public function exists() + public function exists(): bool { + $id = $this->requireId('exists'); + return $this->index->getClient()->exists([ 'index' => $this->index->name(), - 'id' => $this->id, + 'id' => $id, ])->asBool(); } @@ -121,11 +123,13 @@ public function exists() * @param bool $upsert * @return array */ - public function update($data, $upsert = false) + public function update(array $data, bool $upsert = false): array { + $id = $this->requireId('update'); + $params = [ 'index' => $this->index->name(), - 'id' => $this->id, + 'id' => $id, 'body' => [ 'doc' => $data, 'doc_as_upsert' => $upsert, @@ -148,17 +152,23 @@ public function update($data, $upsert = false) /** * Index (create or overwrite) the document. * - * @param array $document + * If $id is null or empty, ES auto-generates an id. + * + * @param array $data * @return array */ - public function index($document) + public function index(array $data): array { $params = [ 'index' => $this->index->name(), - 'id' => $this->id, - 'body' => $document, ]; + if ($this->id !== null && $this->id !== '') { + $params['id'] = $this->id; + } + + $params['body'] = $data; + if ($this->refresh !== null) { $params['refresh'] = $this->refresh; } @@ -171,29 +181,36 @@ public function index($document) /** * Alias for index(). Create or overwrite the document. * - * @param array $document + * @param array $data * @return array */ - public function save($document) + public function save(array $data): array { - return $this->index($document); + return $this->index($data); } /** * Create the document (fail if already exists). * - * @param array $document + * If $id is null or empty, ES auto-generates an id (always a create, since + * auto-generated ids are unique). + * + * @param array $data * @return array */ - public function create($document) + public function create(array $data): array { $params = [ 'index' => $this->index->name(), - 'id' => $this->id, - 'body' => $document, - 'op_type' => 'create', ]; + if ($this->id !== null && $this->id !== '') { + $params['id'] = $this->id; + } + + $params['body'] = $data; + $params['op_type'] = 'create'; + if ($this->refresh !== null) { $params['refresh'] = $this->refresh; } @@ -208,11 +225,13 @@ public function create($document) * * @return array */ - public function delete() + public function delete(): array { + $id = $this->requireId('delete'); + $params = [ 'index' => $this->index->name(), - 'id' => $this->id, + 'id' => $id, ]; if ($this->refresh !== null) { @@ -224,6 +243,28 @@ public function delete() return $this->index->getClient()->delete($params)->asArray(); } + /** + * Resolve the document id, throwing for operations that cannot auto-generate. + * + * get/source/exists/update/delete address an existing document and require + * an explicit id; only index/create let ES auto-generate. + * + * @return string|int + * @throws RuntimeException if the id is null or empty + */ + private function requireId(string $operation): string|int + { + if ($this->id === null || $this->id === '') { + throw new RuntimeException(sprintf( + '%s() requires an explicit document id; got null/empty. ' + . 'Use index() or create() to let Elasticsearch auto-generate one.', + $operation + )); + } + + return $this->id; + } + /** * Reset pending options after a write operation. */ diff --git a/src/Index/Event.php b/src/Index/Event.php index 9c250d0..bd8edd1 100644 --- a/src/Index/Event.php +++ b/src/Index/Event.php @@ -1,5 +1,7 @@ */ - private $data = []; + private array $data = []; /** * @param string $name * @param string $index */ - public function __construct($name, $index) + public function __construct(string $name, string $index) { $this->name = $name; $this->index = $index; @@ -47,7 +49,7 @@ public function __construct($name, $index) * @param string $key * @return mixed */ - public function __get($key) + public function __get(string $key): mixed { return $this->data[$key] ?? null; } @@ -56,7 +58,7 @@ public function __get($key) * @param string $key * @param mixed $value */ - public function __set($key, $value) + public function __set(string $key, mixed $value): void { $this->data[$key] = $value; } @@ -65,7 +67,7 @@ public function __set($key, $value) * @param string $key * @return bool */ - public function __isset($key) + public function __isset(string $key): bool { return isset($this->data[$key]); } diff --git a/src/Index/EventDispatcher.php b/src/Index/EventDispatcher.php new file mode 100644 index 0000000..830611b --- /dev/null +++ b/src/Index/EventDispatcher.php @@ -0,0 +1,74 @@ +> + */ + private static array $listeners = []; + + /** + * Register an event listener. + * + * Supports exact event name, category wildcard (e.g. 'search.*'), or global '*'. + * + * @param string $event + * @param callable $listener receives (Event $event) + * @return void + */ + public static function listen(string $event, callable $listener): void + { + self::$listeners[$event][] = $listener; + } + + /** + * Dispatch an event to all matching listeners. + * + * @param Event $event + * @return void + */ + public static function dispatch(Event $event): void + { + foreach (self::$listeners as $pattern => $listeners) { + if ($pattern === $event->name || $pattern === '*' || self::matchesCategory($pattern, $event->name)) { + foreach ($listeners as $listener) { + $listener($event); + } + } + } + } + + /** + * Reset all registered listeners. Mainly for testing. + * + * @return void + */ + public static function reset(): void + { + self::$listeners = []; + } + + /** + * Check if a wildcard pattern matches an event by category. + * + * @param string $pattern + * @param string $event + * @return bool + */ + private static function matchesCategory(string $pattern, string $event): bool + { + if (substr($pattern, -2) !== '.*') { + return false; + } + + $prefix = substr($pattern, 0, -1); + return strpos($event, $prefix) === 0; + } +} diff --git a/src/Index/Index.php b/src/Index/Index.php index 6d93164..35426e0 100644 --- a/src/Index/Index.php +++ b/src/Index/Index.php @@ -1,5 +1,7 @@ - */ - protected static $clients = []; - /** * @var string */ - protected $connection = 'default'; + protected string $connection = 'default'; /** * @var string */ - protected $name; + protected string $name; /** * @var array */ - protected $mappings = []; + protected array $mappings = []; /** * @var array */ - protected $settings = []; + protected array $settings = []; /** * @var int */ - protected $perPage = 15; + protected int $perPage = 15; /** * @var int */ - protected $maxPerPage = 100; - - /** - * @var callable|null - */ - protected static $pageResolver; - - /** - * @var callable|null - */ - protected static $paginatorResolver; - - /** - * @var array> - */ - protected static $listeners = []; + protected int $maxPerPage = 100; /** * Register an Elasticsearch client. Optionally name the connection. * * @param ClientInterface $client - * @param string|null $name connection name, null for default + * @param string $connection connection name, defaults to 'default' * @return void */ - public static function setClient(ClientInterface $client, $name = null) + public static function setClient(ClientInterface $client, string $connection = 'default'): void { - self::$clients[$name ?? 'default'] = $client; + ClientManager::set($client, $connection); } /** @@ -81,160 +63,153 @@ public static function setClient(ClientInterface $client, $name = null) * * @return ClientInterface */ - public function getClient() + public function getClient(): ClientInterface { - if (isset(self::$clients[$this->connection])) { - return self::$clients[$this->connection]; - } - if (isset(self::$clients['default'])) { - return self::$clients['default']; - } - throw new RuntimeException( - 'Elasticsearch client not registered for connection \'' . $this->connection . '\'. ' - . 'Call ' . static::class . '::setClient($client) first.' - ); + return ClientManager::get($this->connection); } /** - * Return the index name. + * Set the connection name for this index instance. * - * @return string + * @param string $connection + * @return $this */ - public function name() + public function setConnection(string $connection): static { - return $this->name; + $this->connection = $connection; + + return $this; } /** - * Create a new Search instance. Supports both static and instance call. + * Return the connection name for this index. * - * @return Search + * @return string */ - public static function query(Query $query = null) + public function getConnection(): string { - return new Search(new static(), $query); + return $this->connection; } /** - * Create a DocReference for a single document. Supports both static and instance call. + * Create a new index instance with the given connection. * - * @param string|int $id - * @return Doc + * @param string $connection + * @return static */ - public static function doc($id) + public static function on(string $connection): static { - return new Doc(new static(), $id); + return (new static())->setConnection($connection); } /** - * Insert (create or overwrite) a single document. + * Return the index name. * - * @param string|int|null $id document ID, null or empty string to let ES auto-generate - * @param array $document document body - * @return array + * @return string */ - public static function insert($id, array $document) + public function name(): string { - $index = new static(); - $params = ['index' => $index->name(), 'body' => $document]; - - if ($id !== null && $id !== '') { - $params['id'] = $id; + if (empty($this->name)) { + throw new RuntimeException( + sprintf('Index $name is not set in %s', static::class) + ); } - return $index->getClient()->index($params)->asArray(); + return $this->name; } /** - * Return the index mapping definition. + * Create a new Search instance from this index instance. * - * @return array + * @param Query|null $query + * @return Search */ - public function mappings() + public function newQuery(?Query $query = null): Search { - return $this->mappings; + return new Search($this, $query); } /** - * Return the index settings definition. + * Create a Search instance. Delegates to newQuery() with a fresh instance. * - * @return array + * @param Query|null $query + * @return Search */ - public function settings() + public static function query(?Query $query = null): Search { - return $this->settings; + return (new static())->newQuery($query); } /** - * Generate the backing index name for rebuild. Override to customize naming. + * Create a Doc reference from this index instance. * - * @return string + * @param string|int|null $id document id, or null/'' to let ES auto-generate + * @return Doc */ - public function rebuildName(): string + public function newDoc(string|int|null $id): Doc { - return $this->name . '_' . date('Ymd_His'); + return new Doc($this, $id); } /** - * Return the default number of results per page. + * Create a Doc reference. Delegates to newDoc() with a fresh instance. * - * @return int + * @param string|int|null $id document id, or null/'' to let ES auto-generate + * @return Doc */ - public function perPage() + public static function doc(string|int|null $id): Doc { - return $this->perPage; + return (new static())->newDoc($id); } /** - * Return the maximum allowed results per page. + * Return the index mapping definition. * - * @return int + * @return array */ - public function maxPerPage() + public function mappings(): array { - return $this->maxPerPage; + return $this->mappings; } /** - * Register a resolver that extracts page and perPage from the request. + * Return the index settings definition. * - * @param callable $resolver returns [$page, $perPage] - * @return void + * @return array */ - public static function setPageResolver(callable $resolver) + public function settings(): array { - self::$pageResolver = $resolver; + return $this->settings; } /** - * Register a resolver that converts Results into a framework paginator. + * Generate the backing index name for rebuild. Override to customize naming. * - * @param callable $resolver receives (Results $results, int $page, int $perPage) - * @return void + * @return string */ - public static function setPaginatorResolver(callable $resolver) + public function rebuildName(): string { - self::$paginatorResolver = $resolver; + return $this->name . '_' . date('Ymd_His'); } /** - * Return the registered page resolver, or null. + * Return the default number of results per page. * - * @return callable|null + * @return int */ - public static function getPageResolver() + public function perPage(): int { - return self::$pageResolver; + return $this->perPage; } /** - * Return the registered paginator resolver, or null. + * Return the maximum allowed results per page. * - * @return callable|null + * @return int */ - public static function getPaginatorResolver() + public function maxPerPage(): int { - return self::$paginatorResolver; + return $this->maxPerPage; } /** @@ -251,49 +226,4 @@ public function source(array $context = []): iterable ); } - /** - * Register an event listener. Supports exact event name, category wildcard (e.g. 'search.*'), or global '*'. - * - * @param string $event - * @param callable $listener receives (Event $event) - * @return void - */ - public static function listen($event, callable $listener) - { - self::$listeners[$event][] = $listener; - } - - /** - * Dispatch an event to all matching listeners. - * - * @param Event $event - * @return void - */ - public static function dispatch(Event $event) - { - foreach (self::$listeners as $pattern => $listeners) { - if ($pattern === $event->name || $pattern === '*' || static::matchesCategory($pattern, $event->name)) { - foreach ($listeners as $listener) { - $listener($event); - } - } - } - } - - /** - * Check if a wildcard pattern matches an event by category. - * - * @param string $pattern - * @param string $event - * @return bool - */ - protected static function matchesCategory($pattern, $event) - { - if (substr($pattern, -2) !== '.*') { - return false; - } - - $prefix = substr($pattern, 0, -1); - return strpos($event, $prefix) === 0; - } } diff --git a/src/Index/Manager.php b/src/Index/Manager.php index f166ac3..1d6054e 100644 --- a/src/Index/Manager.php +++ b/src/Index/Manager.php @@ -1,5 +1,7 @@ index = $index; + public function __construct( + private readonly Index $index + ) { } /** @@ -31,12 +25,12 @@ public function __construct(Index $index) * * @return array */ - public function create() + public function create(): array { $indexName = $this->index->name(); $e = new Event('manager.create.before', $indexName); - Index::dispatch($e); + EventDispatcher::dispatch($e); $mappings = $this->index->mappings(); $settings = $this->index->settings(); @@ -51,7 +45,7 @@ public function create() $e = new Event('manager.create.after', $indexName); $e->response = $response; - Index::dispatch($e); + EventDispatcher::dispatch($e); return $response; } @@ -61,12 +55,12 @@ public function create() * * @return array */ - public function delete() + public function delete(): array { $indexName = $this->resolveIndexName(); $e = new Event('manager.delete.before', $indexName); - Index::dispatch($e); + EventDispatcher::dispatch($e); $response = $this->index->getClient()->indices()->delete([ 'index' => $indexName, @@ -74,7 +68,7 @@ public function delete() $e = new Event('manager.delete.after', $indexName); $e->response = $response; - Index::dispatch($e); + EventDispatcher::dispatch($e); return $response; } @@ -84,7 +78,7 @@ public function delete() * * @return bool */ - public function exists() + public function exists(): bool { return $this->index->getClient()->indices()->exists([ 'index' => $this->index->name(), @@ -96,7 +90,7 @@ public function exists() * * @return array */ - public function get() + public function get(): array { return $this->index->getClient()->indices()->get([ 'index' => $this->index->name(), @@ -108,7 +102,7 @@ public function get() * * @return array */ - public function putMapping() + public function putMapping(): array { return $this->index->getClient()->indices()->putMapping([ 'index' => $this->index->name(), @@ -121,7 +115,7 @@ public function putMapping() * * @return array */ - public function getMapping() + public function getMapping(): array { return $this->index->getClient()->indices()->getMapping([ 'index' => $this->index->name(), @@ -134,7 +128,7 @@ public function getMapping() * @param array $settings * @return array */ - public function putSettings(array $settings) + public function putSettings(array $settings): array { return $this->index->getClient()->indices()->putSettings([ 'index' => $this->index->name(), @@ -147,7 +141,7 @@ public function putSettings(array $settings) * * @return array */ - public function getSettings() + public function getSettings(): array { return $this->index->getClient()->indices()->getSettings([ 'index' => $this->index->name(), @@ -159,7 +153,7 @@ public function getSettings() * * @return array */ - public function refresh() + public function refresh(): array { return $this->index->getClient()->indices()->refresh([ 'index' => $this->index->name(), @@ -172,7 +166,7 @@ public function refresh() * @param array $options ES params: max_num_segments, only_expunge_deletes, flush. * @return array */ - public function forceMerge(array $options = []) + public function forceMerge(array $options = []): array { return $this->index->getClient()->indices()->forcemerge( array_merge(['index' => $this->index->name()], $options) @@ -184,7 +178,7 @@ public function forceMerge(array $options = []) * * @return array */ - public function close() + public function close(): array { return $this->index->getClient()->indices()->close([ 'index' => $this->index->name(), @@ -196,7 +190,7 @@ public function close() * * @return array */ - public function open() + public function open(): array { return $this->index->getClient()->indices()->open([ 'index' => $this->index->name(), @@ -211,7 +205,7 @@ public function open() * @param array $options additional alias options: routing, filter, is_write_index. * @return array */ - public function addAlias($alias, array $options = []) + public function addAlias(string $alias, array $options = []): array { $indexName = $this->resolveIndexName(); @@ -234,7 +228,7 @@ public function addAlias($alias, array $options = []) * @param string $alias * @return array */ - public function removeAlias($alias) + public function removeAlias(string $alias): array { $indexName = $this->resolveIndexName(); @@ -251,12 +245,12 @@ public function removeAlias($alias) * @param string $fromIndex * @return array */ - public function swapAlias($alias, $fromIndex) + public function swapAlias(string $alias, string $fromIndex): array { $indexName = $this->index->name(); $e = new Event('manager.swap_alias.before', $indexName); - Index::dispatch($e); + EventDispatcher::dispatch($e); $response = $this->index->getClient()->indices()->updateAliases([ 'body' => [ @@ -269,7 +263,7 @@ public function swapAlias($alias, $fromIndex) $e = new Event('manager.swap_alias.after', $indexName); $e->response = $response; - Index::dispatch($e); + EventDispatcher::dispatch($e); return $response; } @@ -280,7 +274,7 @@ public function swapAlias($alias, $fromIndex) * * @return array */ - public function getAliases() + public function getAliases(): array { $indexName = $this->resolveIndexName(); @@ -294,14 +288,14 @@ public function getAliases() * * @return string */ - private function resolveIndexName() + private function resolveIndexName(): string { $name = $this->index->name(); $client = $this->index->getClient()->indices(); if ($client->existsAlias(['name' => $name])->asBool()) { $aliases = $client->getAlias(['name' => $name])->asArray(); - return array_key_first($aliases); + return array_key_first($aliases) ?? $name; } return $name; diff --git a/src/Index/Pagination.php b/src/Index/Pagination.php new file mode 100644 index 0000000..7474381 --- /dev/null +++ b/src/Index/Pagination.php @@ -0,0 +1,74 @@ +>|null + * @var callable|iterable>|null */ private $dataSource; - /** - * @param Index $index - */ - public function __construct(Index $index) - { - $this->index = $index; + public function __construct( + private readonly Index $index + ) { } /** @@ -52,21 +54,25 @@ public function __construct(Index $index) * @param int $size * @return $this */ - public function batchSize($size) + public function batchSize(int $size): static { $this->batchSize = $size; return $this; } /** - * Skip bulk import errors instead of throwing. Defaults to false. + * Set a callback to handle bulk import errors. + * + * The callback receives the raw ES response. To continue the rebuild, simply + * return without throwing. To abort, throw an exception from the callback. + * Without an error handler, import errors cause the rebuild to abort. * - * @param bool $skip + * @param callable $handler function (array $response): void * @return $this */ - public function skipErrors($skip = true) + public function onError(callable $handler): static { - $this->skipErrors = $skip; + $this->errorHandler = $handler; return $this; } @@ -76,7 +82,7 @@ public function skipErrors($skip = true) * @param bool $allow * @return $this */ - public function allowEmpty($allow = true) + public function allowEmpty(bool $allow = true): static { $this->allowEmpty = $allow; return $this; @@ -86,10 +92,10 @@ public function allowEmpty($allow = true) * Set a custom data source. Accepts a callable or iterable. * Defaults to Index::source() when not set. * - * @param callable|\Iterator> $source + * @param callable|iterable> $source * @return $this */ - public function source($source) + public function source(callable|iterable $source): static { $this->dataSource = $source; return $this; @@ -103,55 +109,86 @@ public function source($source) * - $name is a real index: throws RuntimeException (convert to alias mode first) * - $name does not exist: create alias pointing to new backing index * + * The lock is always released: on failure doRun()'s exception is rethrown + * (never masked); on success a release failure surfaces as a RuntimeException + * noting the rebuild completed but the lock is stale. + * * @param array $context user-defined context passed to source() * @return array{newIndex: string, oldIndex: string|null} */ - public function run(array $context = []) + public function run(array $context = []): array { - $name = $this->index->name(); - $client = $this->index->getClient()->indices(); - - Index::dispatch(new Event('rebuild.run.before', $name)); - - $newName = $this->createIndex(); + $this->acquireLock(); try { - $this->import($newName, $context); + $result = $this->doRun($context); } catch (\Throwable $e) { - $client->delete(['index' => $newName]); + try { + $this->releaseLock(); + } catch (\Throwable) { + // Ignore release failure; the original exception propagates below. + } throw $e; } - $client->refresh(['index' => $newName]); - - $oldIndex = null; - - if ($client->existsAlias(['name' => $name])->asBool()) { - $oldIndices = array_keys($client->getAlias(['name' => $name])->asArray()); - $oldIndex = $oldIndices[0] ?? null; - $actions = []; - foreach ($oldIndices as $idx) { - $actions[] = ['remove' => ['index' => $idx, 'alias' => $name]]; - } - $actions[] = ['add' => ['index' => $newName, 'alias' => $name]]; - $client->updateAliases(['body' => ['actions' => $actions]]); - } elseif ($client->exists(['index' => $name])->asBool()) { - $client->delete(['index' => $newName]); + // Success: data is swapped, so a release failure means only the lock is stale. + try { + $this->releaseLock(); + } catch (\Throwable $e) { throw new RuntimeException( - "Index [{$name}] is a real index, not an alias. " - . "Rebuild requires an alias to swap atomically. " - . "Delete the index manually or convert it to alias mode before running rebuild." + "Rebuild for [{$this->index->name()}] succeeded but the lock could not be released. " + . "Data is already swapped to the new index. Call forceUnlock() to clear the stale lock.", + 0, + $e ); - } else { - $client->putAlias(['index' => $newName, 'name' => $name]); } - $e = new Event('rebuild.run.after', $name); - $e->newIndex = $newName; - $e->oldIndex = $oldIndex; - Index::dispatch($e); + return $result; + } - return ['newIndex' => $newName, 'oldIndex' => $oldIndex]; + /** + * Forcibly remove a stale lock left by a crashed rebuild. + * + * Under normal circumstances the lock is released automatically by run(). + * Call this only when a previous rebuild crashed and the lock persists. + * + * Idempotent: a 404 (lock index or document missing) is treated as success. + */ + public function forceUnlock(): void + { + try { + $this->index->getClient()->delete([ + 'index' => self::LOCK_INDEX, + 'id' => $this->index->name(), + ]); + } catch (ClientResponseException $e) { + if ($e->getResponse()->getStatusCode() !== 404) { + throw $e; + } + } + } + + /** + * Check whether a rebuild lock is currently held for this index. + * + * Returns false only on a real 404; other ES errors propagate so an + * unreachable cluster is never mistaken for an unlocked index. + * + * @return bool + */ + public function isLocked(): bool + { + try { + return $this->index->getClient()->exists([ + 'index' => self::LOCK_INDEX, + 'id' => $this->index->name(), + ])->asBool(); + } catch (ClientResponseException $e) { + if ($e->getResponse()->getStatusCode() === 404) { + return false; + } + throw $e; + } } /** @@ -187,47 +224,185 @@ public function rollback(string $targetIndex): string throw new RuntimeException("Target index [{$targetIndex}] does not exist"); } + $actions = []; + foreach ($currentList as $idx) { + $actions[] = ['remove' => ['index' => $idx, 'alias' => $name]]; + } + $actions[] = ['add' => ['index' => $targetIndex, 'alias' => $name]]; + $client->updateAliases([ 'body' => [ - 'actions' => [ - ['remove' => ['index' => $currentList[0], 'alias' => $name]], - ['add' => ['index' => $targetIndex, 'alias' => $name]], - ], + 'actions' => $actions, ], ]); return $currentList[0]; } + /** + * Execute the rebuild logic (called after lock is acquired). + * + * @param array $context + * @return array{newIndex: string, oldIndex: string|null} + */ + private function doRun(array $context): array + { + $name = $this->index->name(); + $client = $this->index->getClient()->indices(); + + EventDispatcher::dispatch(new Event('rebuild.run.before', $name)); + + $newIndex = $this->createIndex(); + + try { + $this->import($newIndex, $context); + } catch (\Throwable $e) { + $client->delete(['index' => $newIndex]); + throw $e; + } + + $client->refresh(['index' => $newIndex]); + + $oldIndex = null; + + if ($client->existsAlias(['name' => $name])->asBool()) { + $oldIndices = array_keys($client->getAlias(['name' => $name])->asArray()); + $oldIndex = $oldIndices[0] ?? null; + $actions = []; + foreach ($oldIndices as $idx) { + $actions[] = ['remove' => ['index' => $idx, 'alias' => $name]]; + } + $actions[] = ['add' => ['index' => $newIndex, 'alias' => $name]]; + $client->updateAliases(['body' => ['actions' => $actions]]); + } elseif ($client->exists(['index' => $name])->asBool()) { + $client->delete(['index' => $newIndex]); + throw new RuntimeException( + "Index [{$name}] is a real index, not an alias. " + . "Rebuild requires an alias to swap atomically. " + . "Delete the index manually or convert it to alias mode before running rebuild." + ); + } else { + $client->putAlias(['index' => $newIndex, 'name' => $name]); + } + + $e = new Event('rebuild.run.after', $name); + $e->newIndex = $newIndex; + $e->oldIndex = $oldIndex; + EventDispatcher::dispatch($e); + + return ['newIndex' => $newIndex, 'oldIndex' => $oldIndex]; + } + + /** + * Acquire the distributed lock using ES document create (op_type=create). + * + * @throws RuntimeException if the lock is already held + */ + private function acquireLock(): void + { + $this->ensureLockIndex(); + + $name = $this->index->name(); + + try { + $this->index->getClient()->index([ + 'index' => self::LOCK_INDEX, + 'id' => $name, + 'body' => ['locked_at' => date(\DateTimeInterface::ATOM)], + 'op_type' => 'create', + ]); + } catch (ClientResponseException $e) { + if ($e->getResponse()->getStatusCode() === 409) { + throw new RuntimeException( + "Rebuild for [{$name}] is already running. " + . "Call forceUnlock() if the previous rebuild crashed." + ); + } + throw $e; + } + } + + /** + * Release the lock. Idempotent: only 404 is swallowed; other failures + * (transport, 5xx) propagate so a stale lock stays detectable. + * + * @throws ClientResponseException for any non-404 failure + */ + private function releaseLock(): void + { + try { + $this->index->getClient()->delete([ + 'index' => self::LOCK_INDEX, + 'id' => $this->index->name(), + ]); + } catch (ClientResponseException $e) { + if ($e->getResponse()->getStatusCode() === 404) { + return; // Lock already gone — idempotent + } + throw $e; + } + } + + /** + * Ensure the lock index exists. Handles concurrent creation races. + */ + private function ensureLockIndex(): void + { + $indices = $this->index->getClient()->indices(); + + if ($indices->exists(['index' => self::LOCK_INDEX])->asBool()) { + return; + } + + try { + $indices->create([ + 'index' => self::LOCK_INDEX, + 'body' => [ + 'settings' => [ + 'number_of_shards' => 1, + 'number_of_replicas' => 0, + 'index.hidden' => true, + ], + ], + ]); + } catch (ClientResponseException $e) { + // Race: another process may have created it concurrently + if ($indices->exists(['index' => self::LOCK_INDEX])->asBool()) { + return; + } + throw $e; + } + } + /** * Create a new backing index using mappings/settings from the Index subclass. * * @return string the new backing index name */ - protected function createIndex() + protected function createIndex(): string { - $newName = $this->index->rebuildName(); + $newIndex = $this->index->rebuildName(); $mappings = $this->index->mappings(); $settings = $this->index->settings(); $this->index->getClient()->indices()->create([ - 'index' => $newName, + 'index' => $newIndex, 'body' => [ 'mappings' => empty($mappings) ? new stdClass() : $mappings, 'settings' => empty($settings) ? new stdClass() : $settings, ], ]); - return $newName; + return $newIndex; } /** * Import data into the target index. * - * @param string $newName + * @param string $newIndex * @param array $context */ - protected function import($newName, array $context): void + protected function import(string $newIndex, array $context): void { if ($this->dataSource !== null) { $items = is_callable($this->dataSource) ? ($this->dataSource)($context) : $this->dataSource; @@ -235,7 +410,11 @@ protected function import($newName, array $context): void $items = $this->index->source($context); } - $bulk = (new Bulk($this->index))->target($newName)->batchSize($this->batchSize); + $bulk = (new Bulk($this->index))->target($newIndex)->batchSize($this->batchSize); + if ($this->errorHandler) { + $bulk->onError($this->errorHandler); + } + $count = 0; foreach ($items as $id => $doc) { if (!is_array($doc)) { @@ -250,25 +429,11 @@ protected function import($newName, array $context): void if ($count === 0 && !$this->allowEmpty) { throw new RuntimeException( - "Rebuild imported 0 documents for index [{$newName}]. " + "Rebuild imported 0 documents for index [{$newIndex}]. " . "Call allowEmpty() if this is intentional." ); } - $response = $bulk->execute(); - - if (!empty($response['errors'])) { - $e = new Event('rebuild.import.failed', $this->index->name()); - $e->response = $response; - Index::dispatch($e); - - if (!$this->skipErrors) { - $json = json_encode($response, JSON_UNESCAPED_UNICODE); - if (strlen($json) > 4096) { - $json = substr($json, 0, 4096) . '... [truncated]'; - } - throw new RuntimeException("Bulk import failed for index [{$newName}]: {$json}"); - } - } + $bulk->execute(); } } diff --git a/src/Index/Results.php b/src/Index/Results.php index 878389f..18bc275 100644 --- a/src/Index/Results.php +++ b/src/Index/Results.php @@ -1,5 +1,7 @@ */ - protected $response; + protected array $response; /** * @var int */ - protected $page = 1; + protected int $page = 1; /** * @var int */ - protected $perPage = 15; + protected int $perPage = 15; /** * @var bool */ - protected $paginated = false; + protected bool $paginated = false; /** * @param array $response @@ -44,7 +46,7 @@ public function __construct(array $response) * @param int $perPage * @return $this */ - public function paginate($page, $perPage) + public function paginate(int $page, int $perPage): static { $this->page = $page; $this->perPage = $perPage; @@ -57,7 +59,7 @@ public function paginate($page, $perPage) * * @return int */ - public function total() + public function total(): int { return $this->response['hits']['total']['value'] ?? 0; } @@ -67,7 +69,7 @@ public function total() * * @return array> */ - public function hits() + public function hits(): array { return $this->response['hits']['hits'] ?? []; } @@ -77,7 +79,7 @@ public function hits() * * @return array|null> */ - public function docs() + public function docs(): array { return array_column($this->hits(), '_source'); } @@ -87,7 +89,7 @@ public function docs() * * @return array */ - public function ids() + public function ids(): array { return array_column($this->hits(), '_id'); } @@ -97,20 +99,20 @@ public function ids() * * @return array|null */ - public function first() + public function first(): ?array { $docs = $this->docs(); return $docs[0] ?? null; } /** - * Return the aggregations from the response. + * Return the aggregations from the response, or null if none were requested. * - * @return array + * @return array|null */ - public function aggregations() + public function aggregations(): ?array { - return $this->response['aggregations'] ?? []; + return $this->response['aggregations'] ?? null; } /** @@ -118,17 +120,29 @@ public function aggregations() * * @return string|null */ - public function scrollId() + public function scrollId(): ?string { return $this->response['_scroll_id'] ?? null; } /** - * Return whether there are more hits available. + * Return the hits.total.relation value from the Elasticsearch response. + * + * "eq" = total is exact, "gte" = total is a lower bound. + * + * @return string|null "eq" or "gte" + */ + public function totalRelation(): ?string + { + return $this->response['hits']['total']['relation'] ?? null; + } + + /** + * Return whether the current batch contains hits. * * @return bool */ - public function hasMore() + public function hasMore(): bool { return !empty($this->response['hits']['hits']); } @@ -138,7 +152,7 @@ public function hasMore() * * @return int */ - public function took() + public function took(): int { return $this->response['took'] ?? 0; } @@ -148,7 +162,7 @@ public function took() * * @return bool */ - public function timedOut() + public function timedOut(): bool { return $this->response['timed_out'] ?? false; } @@ -158,7 +172,7 @@ public function timedOut() * * @return array */ - public function raw() + public function raw(): array { return $this->response; } @@ -168,7 +182,7 @@ public function raw() * * @return int */ - public function page() + public function page(): int { return $this->page; } @@ -178,7 +192,7 @@ public function page() * * @return int */ - public function perPage() + public function perPage(): int { return $this->perPage; } @@ -188,7 +202,7 @@ public function perPage() * * @return int */ - public function lastPage() + public function lastPage(): int { return (int) ceil($this->total() / $this->perPage) ?: 1; } @@ -198,7 +212,7 @@ public function lastPage() * * @return array|null> */ - public function items() + public function items(): array { return $this->docs(); } @@ -208,7 +222,7 @@ public function items() * * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty($this->response['hits']['hits']); } @@ -227,7 +241,7 @@ public function toPaginator() ); } - $resolver = Index::getPaginatorResolver(); + $resolver = Pagination::getPaginatorResolver(); if ($resolver === null) { throw new RuntimeException( 'Paginator resolver not registered. Call Index::setPaginatorResolver() first.' diff --git a/src/Index/Search.php b/src/Index/Search.php index ea2335a..e5f9e7f 100644 --- a/src/Index/Search.php +++ b/src/Index/Search.php @@ -1,9 +1,12 @@ */ - private $urlParams = []; + private array $urlParams = []; - /** - * @param Index $index - */ - public function __construct(Index $index, Query $query = null) - { + public function __construct( + private readonly Index $index, + ?Query $query = null + ) { $this->query = $query ?? new Query(); - $this->index = $index; } /** @@ -45,7 +41,7 @@ public function __construct(Index $index, Query $query = null) * @param string $routing * @return $this */ - public function routing($routing) + public function routing(string $routing): static { $this->urlParams['routing'] = $routing; return $this; @@ -56,10 +52,10 @@ public function routing($routing) * * @param string $method * @param array $args - * @return $this|mixed + * @return mixed * @throws BadMethodCallException */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { if (!method_exists($this->query, $method)) { throw new BadMethodCallException( @@ -81,7 +77,7 @@ public function __call($method, $args) * * @return Results */ - public function get() + public function get(): Results { return new Results($this->doSearch('get')); } @@ -91,15 +87,16 @@ public function get() * * @return array|null */ - public function first() + public function first(): ?array { $saved = $this->query; $this->query = clone $this->query; $this->query->size(1); - - $response = $this->doSearch('first'); - - $this->query = $saved; + try { + $response = $this->doSearch('first'); + } finally { + $this->query = $saved; + } $docs = (new Results($response))->docs(); return $docs[0] ?? null; @@ -110,9 +107,15 @@ public function first() * * @return int */ - public function count() + public function count(): int { - return $this->doCount()['count']; + $response = $this->doCount(); + + if (!isset($response['count'])) { + throw new RuntimeException('Missing "count" in Elasticsearch response.'); + } + + return $response['count']; } /** @@ -122,7 +125,7 @@ public function count() * @param string $duration * @return Results */ - public function scroll($scrollId = null, $duration = '5m') + public function scroll(?string $scrollId = null, string $duration = '5m'): Results { if ($scrollId !== null) { return $this->doScroll($scrollId, $duration); @@ -135,9 +138,11 @@ public function scroll($scrollId = null, $duration = '5m') $this->query->size(1000); } - $response = $this->doSearch('scroll', ['scroll' => $duration]); - - $this->query = $saved; + try { + $response = $this->doSearch('scroll', ['scroll' => $duration]); + } finally { + $this->query = $saved; + } return new Results($response); } @@ -149,7 +154,7 @@ public function scroll($scrollId = null, $duration = '5m') * @param string $duration * @return Results */ - public function next(Results $results, $duration = '5m') + public function next(Results $results, string $duration = '5m'): Results { return $this->doScroll($results->scrollId(), $duration); } @@ -160,7 +165,7 @@ public function next(Results $results, $duration = '5m') * @param Results $results * @return void */ - public function clear(Results $results) + public function clear(Results $results): void { $scrollId = $results->scrollId(); if ($scrollId !== null) { @@ -177,14 +182,14 @@ public function clear(Results $results) * @param string $duration * @return Results */ - protected function doScroll($scrollId, $duration) + protected function doScroll(string $scrollId, string $duration): Results { $indexName = $this->index->name(); $e = new Event('search.scroll.before', $indexName); $e->action = 'scroll'; $e->scrollId = $scrollId; - Index::dispatch($e); + EventDispatcher::dispatch($e); $start = microtime(true); $response = $this->index->getClient()->scroll([ @@ -199,18 +204,19 @@ protected function doScroll($scrollId, $duration) $e->scrollId = $scrollId; $e->response = $response; $e->duration = $durationTime; - Index::dispatch($e); + EventDispatcher::dispatch($e); return new Results($response); } /** - * Return a generator that yields Results batches via scroll. + * Lazily yield Results batches via scroll. Each yielded Results is one + * scroll batch; the scroll context is cleared when iteration ends. * - * @param string $duration - * @return \Generator + * @param string $duration scroll keep-alive + * @return \Generator */ - public function cursor($duration = '5m') + public function chunk(string $duration = '5m'): \Generator { $results = $this->scroll(null, $duration); @@ -224,6 +230,22 @@ public function cursor($duration = '5m') } } + /** + * Lazily yield individual search hits, flattened across scroll batches. + * + * Each value is a raw ES hit (_id, _score, _source, ...) — the same shape + * as Results::hits() entries. Reuses chunk()'s scroll cleanup. + * + * @param string $duration scroll keep-alive + * @return \Generator, mixed, void> + */ + public function cursor(string $duration = '5m'): \Generator + { + foreach ($this->chunk($duration) as $results) { + yield from $results->hits(); + } + } + /** * Execute a paginated search. Uses pageResolver if no arguments are given. * @@ -231,10 +253,10 @@ public function cursor($duration = '5m') * @param int|null $perPage * @return Results */ - public function paginate($page = null, $perPage = null) + public function paginate(?int $page = null, ?int $perPage = null): Results { if ($page === null && $perPage === null) { - $resolver = Index::getPageResolver(); + $resolver = Pagination::getPageResolver(); if ($resolver !== null) { [$page, $perPage] = $resolver(); } @@ -252,10 +274,11 @@ public function paginate($page = null, $perPage = null) $this->query = clone $this->query; $this->query->from(($page - 1) * $perPage); $this->query->size($perPage); - - $response = $this->doSearch('paginate'); - - $this->query = $saved; + try { + $response = $this->doSearch('paginate'); + } finally { + $this->query = $saved; + } return (new Results($response))->paginate($page, $perPage); } @@ -265,7 +288,7 @@ public function paginate($page = null, $perPage = null) * * @return array */ - protected function doCount() + protected function doCount(): array { $indexName = $this->index->name(); $body = $this->query->toArray() ?: new stdClass(); @@ -273,7 +296,7 @@ protected function doCount() $e = new Event('search.query.before', $indexName); $e->dsl = $body; $e->action = 'count'; - Index::dispatch($e); + EventDispatcher::dispatch($e); $start = microtime(true); $response = $this->index->getClient()->count([ @@ -288,7 +311,7 @@ protected function doCount() $e->response = $response; $e->duration = $duration; $e->action = 'count'; - Index::dispatch($e); + EventDispatcher::dispatch($e); return $response; } @@ -300,7 +323,7 @@ protected function doCount() * @param array $extra extra request params (e.g. scroll) * @return array */ - protected function doSearch($action, array $extra = []) + protected function doSearch(string $action, array $extra = []): array { $indexName = $this->index->name(); $body = $this->query->toArray() ?: new stdClass(); @@ -308,7 +331,7 @@ protected function doSearch($action, array $extra = []) $e = new Event('search.query.before', $indexName); $e->dsl = $body; $e->action = $action; - Index::dispatch($e); + EventDispatcher::dispatch($e); $params = array_merge(['index' => $indexName, 'body' => $body], $this->urlParams, $extra); @@ -322,7 +345,7 @@ protected function doSearch($action, array $extra = []) $e->response = $response; $e->duration = $duration; $e->action = $action; - Index::dispatch($e); + EventDispatcher::dispatch($e); return $response; } diff --git a/src/Index/StatsSupport.php b/src/Index/StatsSupport.php new file mode 100644 index 0000000..f25627e --- /dev/null +++ b/src/Index/StatsSupport.php @@ -0,0 +1,109 @@ +aggregateScalar('max', $field); + } + + /** + * Return the minimum value of a field. + * + * @param string $field + * @return float|null + */ + public function min(string $field): ?float + { + return $this->aggregateScalar('min', $field); + } + + /** + * Return the average value of a field. + * + * @param string $field + * @return float|null + */ + public function avg(string $field): ?float + { + return $this->aggregateScalar('avg', $field); + } + + /** + * Return the sum of a field. + * + * @param string $field + * @return float|null + */ + public function sum(string $field): ?float + { + return $this->aggregateScalar('sum', $field); + } + + /** + * Return stats (count, min, max, avg, sum) for a field in a single request. + * + * @param string $field + * @return array{count: int, min: float|null, max: float|null, avg: float|null, sum: float|null}|null + */ + public function stats(string $field): ?array + { + $saved = $this->query; + $this->query = clone $this->query; + $this->query->size(0); + $this->query->aggs('__stats', ['stats' => ['field' => $field]]); + try { + $response = $this->doSearch('stats'); + } finally { + $this->query = $saved; + } + + $raw = $response['aggregations']['__stats'] ?? null; + if ($raw === null) { + return null; + } + + return [ + 'count' => $raw['count'] ?? 0, + 'min' => $raw['min'] ?? null, + 'max' => $raw['max'] ?? null, + 'avg' => $raw['avg'] ?? null, + 'sum' => $raw['sum'] ?? null, + ]; + } + + /** + * Execute a metric aggregation and return the scalar value. + * + * @param string $type + * @param string $field + * @return float|null + */ + private function aggregateScalar(string $type, string $field): ?float + { + $saved = $this->query; + $this->query = clone $this->query; + $this->query->size(0); + $this->query->aggs('__scalar', [$type => ['field' => $field]]); + try { + $response = $this->doSearch($type); + } finally { + $this->query = $saved; + } + + return $response['aggregations']['__scalar']['value'] ?? null; + } +} diff --git a/tests/AggsTest.php b/tests/AggsTest.php index 2b39e8d..6de118f 100644 --- a/tests/AggsTest.php +++ b/tests/AggsTest.php @@ -745,7 +745,7 @@ public function testGlobalAggregation() $query = new Query(); $query->matchAll(); $query->aggs('all', function ($a) { - $a->globalAggregation(); + $a->global(); }); $this->assertQuery($expectedJson, $query); } diff --git a/tests/CompoundQueriesTest.php b/tests/CompoundQueriesTest.php index d65a4ec..65907fd 100644 --- a/tests/CompoundQueriesTest.php +++ b/tests/CompoundQueriesTest.php @@ -585,8 +585,8 @@ public function testBoolAddMustWithNode() { $query = new Query(); $query->bool(function (Boolean $b) { - $b->addMust(new \ElasticKit\DSL\Queries\FullText\Match_('title', 'test')); - $b->addFilter(new \ElasticKit\DSL\Queries\TermLevel\Term('status', 'published')); + $b->must(new \ElasticKit\DSL\Queries\FullText\Match_('title', 'test')); + $b->filter(new \ElasticKit\DSL\Queries\TermLevel\Term('status', 'published')); }); $expectedJson = << 'nike', 'color' => 'red']; $query = new Query(); $query->bool(function (Boolean $b) use ($filters) { - $b->addMust(function (Query $q) { + $b->must(function (Query $q) { $q->match('title', 'shoes'); }); foreach ($filters as $field => $value) { - $b->addFilter(new \ElasticKit\DSL\Queries\TermLevel\Term($field, $value)); + $b->filter(new \ElasticKit\DSL\Queries\TermLevel\Term($field, $value)); } }); $expectedJson = <<disMax(function (DisjunctionMax $dm) { - $dm->addQuery(function (Query $q) { + $dm->queries(function (Query $q) { $q->term('title', 'Quick pets'); }); - $dm->addQuery(function (Query $q) { + $dm->queries(function (Query $q) { $q->term('body', 'Quick pets'); }); $dm->tieBreaker(0.7); diff --git a/tests/DslTestCase.php b/tests/DslTestCase.php index 7e01d1b..548c82e 100644 --- a/tests/DslTestCase.php +++ b/tests/DslTestCase.php @@ -40,7 +40,7 @@ public static function setUpBeforeClass(): void } /** - * (Required, string) Assert Query produces expected JSON, and optionally validate against ES. + * Assert Query produces expected JSON, and optionally validate against ES. * * @param $expectedJson * @param $query diff --git a/tests/FullTextQueriesTest.php b/tests/FullTextQueriesTest.php index 054e72e..5d07a84 100644 --- a/tests/FullTextQueriesTest.php +++ b/tests/FullTextQueriesTest.php @@ -503,6 +503,30 @@ public function testIntervalsWildcard() $this->assertQuery($exampleJson, $query); } + public function testIntervalsRegexp() + { + $exampleJson = <<intervals('my_text', function (Intervals $intervals) { + $intervals->regexp(function (Intervals\Regexp $r) { + $r->pattern('h[ao]t'); + }); + }); + $this->assertQuery($exampleJson, $query); + } + public function testIntervalsRange() { $exampleJson = <<setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); } protected function createIndex($name = 'products') @@ -269,4 +268,85 @@ public function testExecuteReturnsEmptyWhenBodyIsEmpty() $this->assertEquals([], $result); } + + public function testExecuteThrowsOnErrors() + { + $client = $this->createMock(TestClient::class); + $client->method('bulk')->willReturn(new ArrayResponse([ + 'errors' => true, + 'items' => [ + ['index' => ['_id' => '1', 'status' => 400, 'error' => ['type' => 'mapper_parsing_exception']]], + ], + ])); + Index::setClient($client); + + $index = $this->createIndex('products'); + $this->expectException(\RuntimeException::class); + (new Bulk($index))->index('1', ['title' => 'foo'])->execute(); + } + + public function testExecuteCallsOnError() + { + $client = $this->createMock(TestClient::class); + $errorResponse = [ + 'errors' => true, + 'items' => [ + ['index' => ['_id' => '1', 'status' => 400, 'error' => ['type' => 'mapper_parsing_exception']]], + ], + ]; + $client->method('bulk')->willReturn(new ArrayResponse($errorResponse)); + Index::setClient($client); + + $received = null; + $index = $this->createIndex('products'); + (new Bulk($index)) + ->onError(function ($response) use (&$received) { + $received = $response; + }) + ->index('1', ['title' => 'foo']) + ->execute(); + + $this->assertEquals($errorResponse, $received); + } + + public function testAutoFlushCallsOnError() + { + $errorResponse = [ + 'errors' => true, + 'items' => [ + ['index' => ['_id' => '1', 'status' => 400, 'error' => ['type' => 'mapper_parsing_exception']]], + ], + ]; + $client = $this->createMock(TestClient::class); + $client->method('bulk')->willReturn(new ArrayResponse($errorResponse)); + Index::setClient($client); + + $received = null; + $index = $this->createIndex('products'); + $bulk = (new Bulk($index)) + ->batchSize(1) + ->onError(function ($response) use (&$received) { + $received = $response; + }); + + $bulk->index('1', ['title' => 'foo']); // triggers auto-flush + + $this->assertEquals($errorResponse, $received); + } + + public function testAutoFlushThrowsOnErrorsWithoutHandler() + { + $client = $this->createMock(TestClient::class); + $client->method('bulk')->willReturn(new ArrayResponse([ + 'errors' => true, + 'items' => [], + ])); + Index::setClient($client); + + $index = $this->createIndex('products'); + $bulk = (new Bulk($index))->batchSize(1); + + $this->expectException(\RuntimeException::class); + $bulk->index('1', ['title' => 'foo']); // auto-flush throws + } } diff --git a/tests/Index/DocTest.php b/tests/Index/DocTest.php index 71168f4..949fac1 100644 --- a/tests/Index/DocTest.php +++ b/tests/Index/DocTest.php @@ -1,6 +1,7 @@ setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); } protected function createIndex($name = 'products') @@ -332,4 +331,27 @@ public function testDeleteWithRefresh() $index = $this->createIndex('products'); $index->doc('1')->refresh('wait_for')->delete(); } + + public function testIndexAutoGeneratesIdWhenEmpty() + { + $client = $this->createMock(TestClient::class); + $client->expects($this->once()) + ->method('index') + ->with(['index' => 'products', 'body' => ['title' => 'foo']]) + ->willReturn(new ArrayResponse(['result' => 'created', '_id' => 'auto'])); + Index::setClient($client); + + $index = $this->createIndex('products'); + $result = $index->newDoc(null)->index(['title' => 'foo']); + + $this->assertEquals('created', $result['result']); + } + + public function testUpdateRequiresExplicitId() + { + $index = $this->createIndex('products'); + + $this->expectException(\RuntimeException::class); + $index->newDoc(null)->update(['title' => 'foo']); + } } diff --git a/tests/Index/EventTest.php b/tests/Index/EventTest.php index 09edd1f..8d01c92 100644 --- a/tests/Index/EventTest.php +++ b/tests/Index/EventTest.php @@ -1,20 +1,17 @@ setAccessible(true); - $ref->setValue(null, []); - - $clientRef = new ReflectionProperty(Index::class, 'clients'); - $clientRef->setAccessible(true); - $clientRef->setValue(null, []); + EventDispatcher::reset(); + ClientManager::reset(); } protected function createIndex($name = 'products') @@ -40,7 +37,7 @@ protected function mockClient() public function testListenAndDispatch() { $received = null; - Index::listen('search.query.before', function (Event $e) use (&$received) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$received) { $received = $e; }); @@ -56,7 +53,7 @@ public function testListenAndDispatch() public function testSearchBeforePassesDsl() { $dsl = null; - Index::listen('search.query.before', function (Event $e) use (&$dsl) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$dsl) { $dsl = $e->dsl; }); @@ -71,7 +68,7 @@ public function testSearchBeforePassesDsl() public function testSearchAfterPassesResponse() { $response = null; - Index::listen('search.query.after', function (Event $e) use (&$response) { + EventDispatcher::listen('search.query.after', function (Event $e) use (&$response) { $response = $e->response; }); @@ -86,7 +83,7 @@ public function testSearchAfterPassesResponse() public function testSearchAfterContainsDuration() { $duration = null; - Index::listen('search.query.after', function (Event $e) use (&$duration) { + EventDispatcher::listen('search.query.after', function (Event $e) use (&$duration) { $duration = $e->duration; }); @@ -101,7 +98,7 @@ public function testSearchAfterContainsDuration() public function testSearchEventPassesAction() { $action = null; - Index::listen('search.query.before', function (Event $e) use (&$action) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$action) { $action = $e->action; }); @@ -115,7 +112,7 @@ public function testSearchEventPassesAction() public function testFirstTriggersSearchWithAction() { $action = null; - Index::listen('search.query.before', function (Event $e) use (&$action) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$action) { $action = $e->action; }); @@ -129,7 +126,7 @@ public function testFirstTriggersSearchWithAction() public function testWildcardListener() { $events = []; - Index::listen('*', function (Event $e) use (&$events) { + EventDispatcher::listen('*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -144,7 +141,7 @@ public function testWildcardListener() public function testCategoryWildcardListener() { $events = []; - Index::listen('search.*', function (Event $e) use (&$events) { + EventDispatcher::listen('search.*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -159,7 +156,7 @@ public function testCategoryWildcardListener() public function testCategoryWildcardMatchesSearchQueryEvents() { $events = []; - Index::listen('search.*', function (Event $e) use (&$events) { + EventDispatcher::listen('search.*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -181,10 +178,10 @@ public function testCategoryWildcardMatchesSearchQueryEvents() public function testMultipleListeners() { $count = 0; - Index::listen('search.query.before', function (Event $e) use (&$count) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$count) { $count++; }); - Index::listen('search.query.before', function (Event $e) use (&$count) { + EventDispatcher::listen('search.query.before', function (Event $e) use (&$count) { $count++; }); @@ -207,7 +204,7 @@ public function testNoListenersDoesNotError() public function testBulkExecutePassesActions() { $actions = null; - Index::listen('bulk.execute.before', function (Event $e) use (&$actions) { + EventDispatcher::listen('bulk.execute.before', function (Event $e) use (&$actions) { $actions = $e->actions; }); @@ -227,7 +224,7 @@ public function testBulkExecutePassesActions() public function testManagerCreateEvents() { $events = []; - Index::listen('manager.create.*', function (Event $e) use (&$events) { + EventDispatcher::listen('manager.create.*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -248,7 +245,7 @@ public function testManagerCreateEvents() public function testManagerDeleteEvents() { $events = []; - Index::listen('manager.delete.*', function (Event $e) use (&$events) { + EventDispatcher::listen('manager.delete.*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -270,7 +267,7 @@ public function testManagerDeleteEvents() public function testManagerReadOperationsHaveNoEvents() { $events = []; - Index::listen('*', function (Event $e) use (&$events) { + EventDispatcher::listen('*', function (Event $e) use (&$events) { $events[] = $e->name; }); @@ -297,7 +294,7 @@ public function testManagerReadOperationsHaveNoEvents() public function testRebuildRunBeforeAndAfterEvents() { $events = []; - Index::listen('rebuild.*', function (Event $e) use (&$events) { + EventDispatcher::listen('rebuild.*', function (Event $e) use (&$events) { $events[] = $e->name; }); diff --git a/tests/Index/IndexTest.php b/tests/Index/IndexTest.php index acc5d73..2c2b325 100644 --- a/tests/Index/IndexTest.php +++ b/tests/Index/IndexTest.php @@ -2,7 +2,9 @@ use PHPUnit\Framework\TestCase; use ElasticKit\DSL\Query; +use ElasticKit\Index\ClientManager; use ElasticKit\Index\Index; +use ElasticKit\Index\Pagination; use ElasticKit\Index\Results; use ElasticKit\Index\Search; @@ -15,19 +17,8 @@ protected function setUp(): void protected function tearDown(): void { - // Reset static clients between tests - $ref = new ReflectionProperty(Index::class, 'clients'); - $ref->setAccessible(true); - $ref->setValue(null, []); - - // Reset static resolvers between tests - $pageRef = new ReflectionProperty(Index::class, 'pageResolver'); - $pageRef->setAccessible(true); - $pageRef->setValue(null, null); - - $paginatorRef = new ReflectionProperty(Index::class, 'paginatorResolver'); - $paginatorRef->setAccessible(true); - $paginatorRef->setValue(null, null); + ClientManager::reset(); + Pagination::reset(); } protected function createIndex($name = 'products') @@ -388,7 +379,7 @@ public function testClearSkipsWhenNoScrollId() $index->query()->clear($results); } - public function testCursorYieldsResultsBatches() + public function testChunkYieldsResultsBatches() { $client = $this->createMock(TestClient::class); @@ -426,7 +417,7 @@ public function testCursorYieldsResultsBatches() $index = $this->createIndex('products'); $batches = []; - foreach ($index->query()->matchAll()->cursor('1m') as $results) { + foreach ($index->query()->matchAll()->chunk('1m') as $results) { $this->assertInstanceOf(Results::class, $results); $batches[] = $results; } @@ -437,6 +428,55 @@ public function testCursorYieldsResultsBatches() $this->assertEquals(['3'], $batches[1]->ids()); } + public function testCursorYieldsIndividualHits() + { + $client = $this->createMock(TestClient::class); + + $client->method('search')->willReturn(new ArrayResponse([ + '_scroll_id' => 'scroll1', + 'hits' => [ + 'total' => ['value' => 3], + 'hits' => [ + ['_id' => '1', '_source' => ['id' => 1]], + ['_id' => '2', '_source' => ['id' => 2]], + ], + ], + ])); + + $callCount = 0; + $client->method('scroll')->willReturnCallback(function () use (&$callCount) { + $callCount++; + if ($callCount === 1) { + return new ArrayResponse([ + '_scroll_id' => 'scroll2', + 'hits' => [ + 'total' => ['value' => 3], + 'hits' => [['_id' => '3', '_source' => ['id' => 3]]], + ], + ]); + } + return new ArrayResponse([ + '_scroll_id' => 'scroll3', + 'hits' => ['total' => ['value' => 3], 'hits' => []], + ]); + }); + + $client->method('clearScroll'); + Index::setClient($client); + + $index = $this->createIndex('products'); + $hits = []; + foreach ($index->query()->matchAll()->cursor('1m') as $hit) { + $hits[] = $hit; + } + + // 3 docs flattened across 2 batches, each yielded as a raw hit + $this->assertCount(3, $hits); + $this->assertSame('1', $hits[0]['_id']); + $this->assertSame(['id' => 1], $hits[0]['_source']); + $this->assertSame('3', $hits[2]['_id']); + } + public function testNameReturnsIndexName() { $index = $this->createIndex('orders'); @@ -476,7 +516,7 @@ public function testPaginateWithExplicitParams() public function testPaginateWithPageResolver() { - Index::setPageResolver(function () { + Pagination::setPageResolver(function () { return [2, 20]; }); @@ -606,7 +646,7 @@ public function __construct($name = 'products') public function testToPaginatorReturnsFrameworkPaginator() { - Index::setPaginatorResolver(function (Results $results) { + Pagination::setPaginatorResolver(function (Results $results) { return [ 'data' => $results->items(), 'total' => $results->total(), @@ -685,11 +725,14 @@ public function __construct($name) $this->assertSame($logClient, $logs->getClient()); } - public function testGetClientFallsBackToDefault() + public function testGetClientThrowsForUnknownConnection() { $defaultClient = $this->createMock(TestClient::class); Index::setClient($defaultClient); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('nonexistent'); + $index = new class('unknown') extends Index { public function __construct($name) { @@ -698,27 +741,127 @@ public function __construct($name) } }; - $this->assertSame($defaultClient, $index->getClient()); + $index->getClient(); } public function testGetClientThrowsWhenNotRegistered() { - // Ensure no clients are registered - $ref = new ReflectionProperty(Index::class, 'clients'); - $ref->setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('nonexistent'); - - $index = new class('test') extends Index { - public function __construct($name) - { - $this->name = $name; - $this->connection = 'nonexistent'; - } - }; + $index = $this->createIndex('test'); $index->getClient(); } + + public function testSetConnectionAndGetConnection() + { + $index = $this->createIndex('products'); + + $this->assertEquals('default', $index->getConnection()); + + $result = $index->setConnection('secondary'); + $this->assertSame($index, $result); + $this->assertEquals('secondary', $index->getConnection()); + } + + public function testOnCreatesInstanceWithConnection() + { + $defaultClient = $this->createMock(TestClient::class); + $secondaryClient = $this->createMock(TestClient::class); + Index::setClient($defaultClient); + Index::setClient($secondaryClient, 'secondary'); + + $index = TestConcreteIndex::on('secondary'); + + $this->assertInstanceOf(TestConcreteIndex::class, $index); + $this->assertEquals('secondary', $index->getConnection()); + $this->assertEquals('test', $index->name()); + $this->assertSame($secondaryClient, $index->getClient()); + } + + public function testNewQueryReturnsSearch() + { + $client = $this->createMock(TestClient::class); + $client->method('search')->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 1], 'hits' => [['_source' => ['title' => 'test']]]], + ])); + Index::setClient($client); + + $index = $this->createIndex('products'); + $this->assertInstanceOf(Search::class, $index->newQuery()); + + $query = Query::create()->match('title', 'test'); + $results = $index->newQuery($query)->get(); + $this->assertEquals(1, $results->total()); + } + + public function testNewQueryUsesInstanceConnection() + { + $secondaryClient = $this->createMock(TestClient::class); + $secondaryClient->method('search')->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 0], 'hits' => []], + ])); + Index::setClient($secondaryClient, 'secondary'); + + $index = $this->createIndex('products'); + $index->setConnection('secondary'); + $results = $index->newQuery()->matchAll()->get(); + + $this->assertEquals(0, $results->total()); + } + + public function testNewDocUsesInstanceConnection() + { + $secondaryClient = $this->createMock(TestClient::class); + $secondaryClient->method('getSource')->willReturn(new ArrayResponse([ + 'title' => 'test', + ])); + Index::setClient($secondaryClient, 'secondary'); + + $index = $this->createIndex('products'); + $index->setConnection('secondary'); + $doc = $index->newDoc(1); + + $this->assertInstanceOf(\ElasticKit\Index\Doc::class, $doc); + $this->assertEquals(['title' => 'test'], $doc->source()); + } + + public function testQueryDocDelegateToNew() + { + $this->assertInstanceOf(Search::class, TestConcreteIndex::query()); + $this->assertInstanceOf(\ElasticKit\Index\Doc::class, TestConcreteIndex::doc(1)); + } + + public function testOnNewQueryChain() + { + $secondaryClient = $this->createMock(TestClient::class); + $secondaryClient->method('search')->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 1], 'hits' => [['_source' => ['title' => 'from_secondary']]]], + ])); + Index::setClient($secondaryClient, 'secondary'); + + $results = TestConcreteIndex::on('secondary')->newQuery()->matchAll()->get(); + + $this->assertEquals(1, $results->total()); + $this->assertEquals([['title' => 'from_secondary']], $results->docs()); + } + + public function testOnNewDocChain() + { + $secondaryClient = $this->createMock(TestClient::class); + $secondaryClient->method('getSource')->willReturn(new ArrayResponse([ + 'title' => 'secondary_doc', + ])); + Index::setClient($secondaryClient, 'secondary'); + + $doc = TestConcreteIndex::on('secondary')->newDoc(42); + + $this->assertEquals(['title' => 'secondary_doc'], $doc->source()); + } +} + +class TestConcreteIndex extends Index +{ + protected string $name = 'test'; } diff --git a/tests/Index/ManagerTest.php b/tests/Index/ManagerTest.php index d1bdbac..5039b72 100644 --- a/tests/Index/ManagerTest.php +++ b/tests/Index/ManagerTest.php @@ -1,6 +1,7 @@ setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); } protected function createIndex($name = 'products', $mappings = [], $settings = []) diff --git a/tests/Index/RebuildTest.php b/tests/Index/RebuildTest.php index 7e8b79c..5412571 100644 --- a/tests/Index/RebuildTest.php +++ b/tests/Index/RebuildTest.php @@ -1,6 +1,7 @@ setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); } protected function createIndex($name = 'products', $mappings = [], $settings = []) @@ -40,13 +39,20 @@ public function testRunCreatesBackingIndexAndSetsAlias() }))->willReturn(new ArrayResponse(['acknowledged' => true])); $indices->method('existsAlias')->willReturn(new BoolResponse(false)); - $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); $indices->expects($this->once())->method('putAlias')->with($this->callback(function ($params) { return strpos($params['index'], 'products_') === 0 && $params['name'] === 'products'; }))->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->expects($this->once())->method('bulk')->with($this->callback(function ($params) { return count($params['body']) === 4 && $params['body'][0]['index']['_id'] === 1 @@ -78,6 +84,7 @@ public function testRunSwapsAliasAtomically() { $indices = $this->createMock(TestIndices::class); $indices->expects($this->once())->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(true)); $indices->method('existsAlias')->willReturn(new BoolResponse(true)); $indices->method('getAlias')->willReturn(new ArrayResponse(['products_v1' => ['aliases' => ['products' => []]]])); $indices->expects($this->once())->method('updateAliases')->with($this->callback(function ($params) { @@ -90,6 +97,8 @@ public function testRunSwapsAliasAtomically() $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -119,6 +128,8 @@ public function testRunThrowsWhenNameIsRealIndex() $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -144,11 +155,18 @@ public function testRunWithBatchSize() $indices = $this->createMock(TestIndices::class); $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); $indices->method('existsAlias')->willReturn(new BoolResponse(false)); - $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->expects($this->exactly(2))->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -174,11 +192,18 @@ public function testRunWithCustomSource() $indices = $this->createMock(TestIndices::class); $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); $indices->method('existsAlias')->willReturn(new BoolResponse(false)); - $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->expects($this->once())->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -199,11 +224,18 @@ public function testRunWithCustomRealName() return strpos($params['index'], 'products_v') === 0; }))->willReturn(new ArrayResponse(['acknowledged' => true])); $indices->method('existsAlias')->willReturn(new BoolResponse(false)); - $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -305,12 +337,15 @@ public function testRunThrowsOnEmptyImport() { $indices = $this->createMock(TestIndices::class); $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(true)); $indices->method('delete')->with($this->callback(function ($params) { return strpos($params['index'], 'products_') === 0; }))->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); Index::setClient($client); $index = new class extends Index { @@ -335,11 +370,18 @@ public function testRunAllowsEmptyImportWithAllowEmpty() $indices = $this->createMock(TestIndices::class); $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); $indices->method('existsAlias')->willReturn(new BoolResponse(false)); - $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); Index::setClient($client); @@ -363,12 +405,15 @@ public function testRunDeletesNewIndexOnImportFailure() { $indices = $this->createMock(TestIndices::class); $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(true)); $indices->expects($this->once())->method('delete')->with($this->callback(function ($params) { return strpos($params['index'], 'products_') === 0; }))->willReturn(new ArrayResponse(['acknowledged' => true])); $client = $this->createMock(TestClient::class); $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); $client->method('bulk')->willReturn(new ArrayResponse(['items' => [], 'errors' => true])); Index::setClient($client); @@ -387,4 +432,398 @@ public function source(array $context = []): iterable $this->expectException(\RuntimeException::class); (new Rebuild($index))->run(); } + + public function testRunAcquiresAndReleasesLock() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); + $indices->method('existsAlias')->willReturn(new BoolResponse(false)); + $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); + $client->expects($this->once())->method('index')->with($this->callback(function ($params) { + return $params['index'] === '.ek_locks' + && $params['id'] === 'products' + && ($params['op_type'] ?? null) === 'create'; + }))->willReturn(new ArrayResponse(['result' => 'created'])); + $client->expects($this->once())->method('delete')->with($this->callback(function ($params) { + return $params['index'] === '.ek_locks' && $params['id'] === 'products'; + }))->willReturn(new ArrayResponse(['result' => 'deleted'])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + return []; + } + }; + + (new Rebuild($index))->allowEmpty()->run(); + } + + public function testRunReleasesLockOnFailure() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(true)); + $indices->method('delete')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $lockReleased = false; + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturnCallback(function () use (&$lockReleased) { + $lockReleased = true; + return new ArrayResponse(['result' => 'deleted']); + }); + $client->method('bulk')->willReturn(new ArrayResponse(['items' => [], 'errors' => true])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + yield 1 => ['title' => 'A']; + } + }; + + try { + (new Rebuild($index))->run(); + } catch (\RuntimeException $e) { + // Expected: bulk import error + } + + $this->assertTrue($lockReleased, 'Lock should be released even when run() fails'); + } + + public function testRunThrowsWhenLocked() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('exists')->willReturn(new BoolResponse(true)); + + $response = $this->createMock(\Psr\Http\Message\ResponseInterface::class); + $response->method('getStatusCode')->willReturn(409); + + $exception = new \Elastic\Elasticsearch\Exception\ClientResponseException(); + $exception->setResponse($response); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willThrowException($exception); + Index::setClient($client); + + $index = $this->createIndex('products'); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('already running'); + (new Rebuild($index))->source([1 => ['title' => 'A']])->run(); + } + + public function testForceUnlock() + { + $client = $this->createMock(TestClient::class); + $client->expects($this->once())->method('delete')->with($this->callback(function ($params) { + return $params['index'] === '.ek_locks' && $params['id'] === 'products'; + }))->willReturn(new ArrayResponse(['result' => 'deleted'])); + Index::setClient($client); + + $index = $this->createIndex('products'); + (new Rebuild($index))->forceUnlock(); + } + + public function testForceUnlockIsIdempotent() + { + $response = $this->createMock(\Psr\Http\Message\ResponseInterface::class); + $response->method('getStatusCode')->willReturn(404); + + $exception = new \Elastic\Elasticsearch\Exception\ClientResponseException(); + $exception->setResponse($response); + + $client = $this->createMock(TestClient::class); + $client->method('delete')->willThrowException($exception); + Index::setClient($client); + + $index = $this->createIndex('products'); + + // Should not throw despite 404 + (new Rebuild($index))->forceUnlock(); + $this->assertTrue(true); // Reached without exception + } + + public function testIsLockedReturnsTrue() + { + $client = $this->createMock(TestClient::class); + $client->method('exists')->with($this->callback(function ($params) { + return $params['index'] === '.ek_locks' && $params['id'] === 'products'; + }))->willReturn(new BoolResponse(true)); + Index::setClient($client); + + $index = $this->createIndex('products'); + $this->assertTrue((new Rebuild($index))->isLocked()); + } + + public function testIsLockedReturnsFalse() + { + $client = $this->createMock(TestClient::class); + $client->method('exists')->willReturn(new BoolResponse(false)); + Index::setClient($client); + + $index = $this->createIndex('products'); + $this->assertFalse((new Rebuild($index))->isLocked()); + } + + public function testEnsureLockIndexCreatesWhenNotExists() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(false); + } + return new BoolResponse(false); + }); + $indices->method('create')->willReturnCallback(function ($params) { + return new ArrayResponse(['acknowledged' => true]); + }); + $indices->method('existsAlias')->willReturn(new BoolResponse(false)); + $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $lockIndexCreated = false; + $indices->expects($this->exactly(2))->method('create')->willReturnCallback(function ($params) use (&$lockIndexCreated) { + if (($params['index'] ?? '') === '.ek_locks') { + $lockIndexCreated = true; + $this->assertEquals(1, $params['body']['settings']['number_of_shards'] ?? 0); + $this->assertEquals(0, $params['body']['settings']['number_of_replicas'] ?? 1); + } + return new ArrayResponse(['acknowledged' => true]); + }); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); + $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + return []; + } + }; + + (new Rebuild($index))->allowEmpty()->run(); + } + + public function testOnErrorReceivesResponse() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('existsAlias')->willReturn(new BoolResponse(false)); + $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $errorResponse = [ + 'errors' => true, + 'items' => [ + ['index' => ['_id' => '1', 'status' => 400, 'error' => ['type' => 'mapper_parsing_exception']]], + ], + ]; + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); + $client->method('bulk')->willReturn(new ArrayResponse($errorResponse)); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + yield 1 => ['title' => 'A']; + } + }; + + $received = null; + (new Rebuild($index)) + ->onError(function ($response) use (&$received) { + $received = $response; + }) + ->run(); + + $this->assertEquals($errorResponse, $received); + } + + public function testOnErrorPreventsException() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(false)); + $indices->method('existsAlias')->willReturn(new BoolResponse(false)); + $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + $client->method('delete')->willReturn(new ArrayResponse(['result' => 'deleted'])); + $client->method('bulk')->willReturn(new ArrayResponse([ + 'errors' => true, + 'items' => [], + ])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + yield 1 => ['title' => 'A']; + } + }; + + // Without onError, would throw. With onError (empty callback), completes successfully. + $result = (new Rebuild($index)) + ->onError(function () {}) + ->run(); + + $this->assertStringStartsWith('products_', $result['newIndex']); + } + + public function testRunThrowsWhenReleaseLockFailsAfterSuccess() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturnCallback(function ($params) { + if (($params['index'] ?? '') === '.ek_locks') { + return new BoolResponse(true); + } + return new BoolResponse(false); + }); + $indices->method('existsAlias')->willReturn(new BoolResponse(false)); + $indices->method('putAlias')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $response = $this->createMock(\Psr\Http\Message\ResponseInterface::class); + $response->method('getStatusCode')->willReturn(503); + $releaseException = new \Elastic\Elasticsearch\Exception\ClientResponseException(); + $releaseException->setResponse($response); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + // 成功路径释放锁时抛 503 + $client->method('delete')->willThrowException($releaseException); + $client->method('bulk')->willReturn(new ArrayResponse(['items' => []])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + yield 1 => ['title' => 'A']; + } + }; + + try { + (new Rebuild($index))->run(); + $this->fail('Expected RuntimeException when lock release fails after success'); + } catch (\RuntimeException $e) { + $this->assertStringContainsString('succeeded but the lock could not be released', $e->getMessage()); + $this->assertStringContainsString('forceUnlock', $e->getMessage()); + $this->assertSame($releaseException, $e->getPrevious()); + } + } + + public function testRunPreservesOriginalExceptionWhenReleaseFails() + { + $indices = $this->createMock(TestIndices::class); + $indices->method('create')->willReturn(new ArrayResponse(['acknowledged' => true])); + $indices->method('exists')->willReturn(new BoolResponse(true)); + $indices->method('delete')->willReturn(new ArrayResponse(['acknowledged' => true])); + + $response = $this->createMock(\Psr\Http\Message\ResponseInterface::class); + $response->method('getStatusCode')->willReturn(503); + $releaseException = new \Elastic\Elasticsearch\Exception\ClientResponseException(); + $releaseException->setResponse($response); + + $client = $this->createMock(TestClient::class); + $client->method('indices')->willReturn($indices); + $client->method('index')->willReturn(new ArrayResponse(['result' => 'created'])); + // 释放锁也失败:503 + $client->method('delete')->willThrowException($releaseException); + // bulk 导入失败 → doRun 抛异常 + $client->method('bulk')->willReturn(new ArrayResponse(['items' => [], 'errors' => true])); + Index::setClient($client); + + $index = new class extends Index { + public function __construct() + { + $this->name = 'products'; + } + + public function source(array $context = []): iterable + { + yield 1 => ['title' => 'A']; + } + }; + + try { + (new Rebuild($index))->run(); + $this->fail('Expected original import exception'); + } catch (\Throwable $e) { + // ⑤:必须抛出 doRun 的原始异常,而非释放锁的 503 ClientResponseException + $this->assertInstanceOf(\RuntimeException::class, $e); + $this->assertStringContainsString('Bulk request has errors', $e->getMessage()); + $this->assertStringNotContainsString('succeeded but the lock', $e->getMessage()); + $this->assertNotSame($releaseException, $e); + } + } + + public function testIsLockedThrowsOnServerError() + { + $response = $this->createMock(\Psr\Http\Message\ResponseInterface::class); + $response->method('getStatusCode')->willReturn(503); + $exception = new \Elastic\Elasticsearch\Exception\ClientResponseException(); + $exception->setResponse($response); + + $client = $this->createMock(TestClient::class); + $client->method('exists')->willThrowException($exception); + Index::setClient($client); + + $index = $this->createIndex('products'); + + $this->expectException(\Elastic\Elasticsearch\Exception\ClientResponseException::class); + (new Rebuild($index))->isLocked(); + } } diff --git a/tests/Index/ResultsTest.php b/tests/Index/ResultsTest.php index 76fb5d5..853b0a4 100644 --- a/tests/Index/ResultsTest.php +++ b/tests/Index/ResultsTest.php @@ -1,26 +1,38 @@ [ - 'total' => ['value' => 42], + 'total' => ['value' => 0, 'relation' => 'eq'], 'hits' => [], ], - ]); - $this->assertEquals(42, $results->total()); + ], $overrides); } - public function testTotalDefaultsToZero() + public function testTotal() { - $results = new Results([]); - $this->assertEquals(0, $results->total()); + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 42, 'relation' => 'eq'], + 'hits' => [], + ], + ])); + $this->assertEquals(42, $results->total()); } public function testHits() @@ -29,103 +41,108 @@ public function testHits() ['_id' => '1', '_source' => ['title' => 'foo']], ['_id' => '2', '_source' => ['title' => 'bar']], ]; - $results = new Results([ - 'hits' => ['total' => ['value' => 2], 'hits' => $hits], - ]); + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 2, 'relation' => 'eq'], + 'hits' => $hits, + ], + ])); $this->assertEquals($hits, $results->hits()); } - public function testHitsDefaultsToEmpty() - { - $results = new Results([]); - $this->assertEquals([], $results->hits()); - } - public function testDocs() { - $results = new Results([ + $results = new Results($this->makeResponse([ 'hits' => [ - 'total' => ['value' => 2], + 'total' => ['value' => 2, 'relation' => 'eq'], 'hits' => [ ['_id' => '1', '_source' => ['title' => 'foo']], ['_id' => '2', '_source' => ['title' => 'bar']], ], ], - ]); + ])); $this->assertEquals([['title' => 'foo'], ['title' => 'bar']], $results->docs()); } - public function testDocsDefaultsToEmpty() - { - $results = new Results([]); - $this->assertEquals([], $results->docs()); - } - public function testAggregations() { $aggs = ['price_avg' => ['value' => 100.5]]; - $results = new Results([ - 'hits' => ['total' => ['value' => 0], 'hits' => []], - 'aggregations' => $aggs, - ]); + $results = new Results($this->makeResponse(['aggregations' => $aggs])); $this->assertEquals($aggs, $results->aggregations()); } - public function testAggregationsDefaultsToEmpty() + public function testAggregationsReturnsNullWhenAbsent() { - $results = new Results(['hits' => []]); - $this->assertEquals([], $results->aggregations()); + $results = new Results($this->makeResponse()); + $this->assertNull($results->aggregations()); } public function testScrollId() { - $results = new Results([ - '_scroll_id' => 'abc123', - 'hits' => ['total' => ['value' => 0], 'hits' => []], - ]); + $results = new Results($this->makeResponse(['_scroll_id' => 'abc123'])); $this->assertEquals('abc123', $results->scrollId()); } - public function testScrollIdDefaultsToNull() + public function testScrollIdReturnsNullWhenAbsent() { - $results = new Results([]); + $results = new Results($this->makeResponse()); $this->assertNull($results->scrollId()); } - public function testHasMore() + public function testHasMoreScrollWithHits() { - $results = new Results([ + $results = new Results($this->makeResponse([ 'hits' => [ - 'total' => ['value' => 1], + 'total' => ['value' => 1, 'relation' => 'eq'], 'hits' => [['_source' => ['title' => 'foo']]], ], - ]); + ])); $this->assertTrue($results->hasMore()); } - public function testHasMoreReturnsFalseWhenEmpty() + public function testHasMoreScrollEmpty() { - $results = new Results([ - 'hits' => ['total' => ['value' => 0], 'hits' => []], - ]); + $results = new Results($this->makeResponse()); $this->assertFalse($results->hasMore()); } + public function testTotalRelationEq() + { + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 42, 'relation' => 'eq'], + 'hits' => [], + ], + ])); + $this->assertEquals('eq', $results->totalRelation()); + } + + public function testTotalRelationGte() + { + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 10000, 'relation' => 'gte'], + 'hits' => [], + ], + ])); + $this->assertEquals('gte', $results->totalRelation()); + } + public function testRaw() { - $response = [ - 'took' => 5, - 'hits' => ['total' => ['value' => 1], 'hits' => []], - ]; + $response = $this->makeResponse(['took' => 5]); $results = new Results($response); $this->assertEquals($response, $results->raw()); } public function testPaginateSetsMetadata() { - $results = new Results([ - 'hits' => ['total' => ['value' => 50], 'hits' => []], - ]); + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 50, 'relation' => 'eq'], + 'hits' => [], + ], + ])); $results->paginate(3, 10); $this->assertEquals(3, $results->page()); @@ -135,9 +152,7 @@ public function testPaginateSetsMetadata() public function testLastPageMinimumIsOne() { - $results = new Results([ - 'hits' => ['total' => ['value' => 0], 'hits' => []], - ]); + $results = new Results($this->makeResponse()); $results->paginate(1, 15); $this->assertEquals(1, $results->lastPage()); @@ -145,88 +160,72 @@ public function testLastPageMinimumIsOne() public function testItemsReturnsDocs() { - $results = new Results([ + $results = new Results($this->makeResponse([ 'hits' => [ - 'total' => ['value' => 2], + 'total' => ['value' => 2, 'relation' => 'eq'], 'hits' => [ ['_id' => '1', '_source' => ['title' => 'foo']], ['_id' => '2', '_source' => ['title' => 'bar']], ], ], - ]); + ])); $this->assertEquals($results->docs(), $results->items()); } public function testIsEmpty() { - $results = new Results([ - 'hits' => ['total' => ['value' => 0], 'hits' => []], - ]); + $results = new Results($this->makeResponse()); $this->assertTrue($results->isEmpty()); - $results = new Results([ + $results = new Results($this->makeResponse([ 'hits' => [ - 'total' => ['value' => 1], + 'total' => ['value' => 1, 'relation' => 'eq'], 'hits' => [['_source' => ['title' => 'foo']]], ], - ]); + ])); $this->assertFalse($results->isEmpty()); } public function testToPaginatorCallsResolver() { - Index::setPaginatorResolver(function (Results $results) { + Pagination::setPaginatorResolver(function (Results $results) { return ['total' => $results->total(), 'page' => $results->page()]; }); - $results = new Results([ - 'hits' => ['total' => ['value' => 50], 'hits' => []], - ]); + $results = new Results($this->makeResponse([ + 'hits' => [ + 'total' => ['value' => 50, 'relation' => 'eq'], + 'hits' => [], + ], + ])); $results->paginate(2, 10); $paginator = $results->toPaginator(); $this->assertEquals(['total' => 50, 'page' => 2], $paginator); - - // Clean up - $ref = new ReflectionProperty(Index::class, 'paginatorResolver'); - $ref->setAccessible(true); - $ref->setValue(null, null); } public function testToPaginatorThrowsWithoutResolver() { - $results = new Results(['hits' => []]); + $results = new Results($this->makeResponse()); $this->expectException(\RuntimeException::class); $results->toPaginator(); } public function testTook() { - $results = new Results([ - 'took' => 5, - 'hits' => ['total' => ['value' => 1], 'hits' => []], - ]); + $results = new Results($this->makeResponse(['took' => 5])); $this->assertEquals(5, $results->took()); } - public function testTookDefaultsToZero() - { - $results = new Results([]); - $this->assertEquals(0, $results->took()); - } - public function testTimedOut() { - $results = new Results([ - 'timed_out' => true, - 'hits' => ['total' => ['value' => 0], 'hits' => []], - ]); + $results = new Results($this->makeResponse(['timed_out' => true])); $this->assertTrue($results->timedOut()); } - public function testTimedOutDefaultsToFalse() + public function testTimedOutFalse() { - $results = new Results([]); + $results = new Results($this->makeResponse(['timed_out' => false])); $this->assertFalse($results->timedOut()); } } diff --git a/tests/Index/AggregationShortcutTest.php b/tests/Index/StatsSupportTest.php similarity index 60% rename from tests/Index/AggregationShortcutTest.php rename to tests/Index/StatsSupportTest.php index 2b3972d..715c084 100644 --- a/tests/Index/AggregationShortcutTest.php +++ b/tests/Index/StatsSupportTest.php @@ -1,10 +1,11 @@ setAccessible(true); - $ref->setValue(null, []); + ClientManager::reset(); } protected function createIndex($name = 'products') @@ -78,7 +77,70 @@ public function testSum() $this->assertEquals(1500.0, $result); } - public function testAggregationShortcutSendsCorrectBody() + public function testStatsReturnsAllMetrics() + { + $client = $this->createMock(TestClient::class); + $client->method('search')->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 0], 'hits' => []], + 'aggregations' => [ + '__stats' => [ + 'count' => 100, + 'min' => 9.99, + 'max' => 199.99, + 'avg' => 49.5, + 'sum' => 4950.0, + ], + ], + ])); + Index::setClient($client); + + $index = $this->createIndex(); + $result = $index->query()->stats('price'); + + $this->assertEquals([ + 'count' => 100, + 'min' => 9.99, + 'max' => 199.99, + 'avg' => 49.5, + 'sum' => 4950.0, + ], $result); + } + + public function testStatsReturnsNullWhenNoAggregation() + { + $client = $this->createMock(TestClient::class); + $client->method('search')->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 0], 'hits' => []], + ])); + Index::setClient($client); + + $index = $this->createIndex(); + $result = $index->query()->stats('nonexistent'); + + $this->assertNull($result); + } + + public function testStatsSendsCorrectBody() + { + $client = $this->createMock(TestClient::class); + $client->expects($this->once())->method('search')->with($this->callback(function ($params) { + $body = $params['body']; + return $body['size'] === 0 + && isset($body['aggs']['__stats']['stats']['field']) + && $body['aggs']['__stats']['stats']['field'] === 'price'; + }))->willReturn(new ArrayResponse([ + 'hits' => ['total' => ['value' => 0], 'hits' => []], + 'aggregations' => [ + '__stats' => ['count' => 0, 'min' => null, 'max' => null, 'avg' => null, 'sum' => 0], + ], + ])); + Index::setClient($client); + + $index = $this->createIndex(); + $index->query()->stats('price'); + } + + public function testScalarSendsCorrectBody() { $client = $this->createMock(TestClient::class); $client->expects($this->once())->method('search')->with($this->callback(function ($params) { @@ -96,7 +158,7 @@ public function testAggregationShortcutSendsCorrectBody() $index->query()->max('price'); } - public function testAggregationShortcutDoesNotMutateQuery() + public function testScalarDoesNotMutateQuery() { $lastBody = null; $client = $this->createMock(TestClient::class); @@ -118,7 +180,7 @@ public function testAggregationShortcutDoesNotMutateQuery() $this->assertEquals(0, $lastBody['size']); } - public function testAggregationShortcutReturnsNullWhenNoValue() + public function testScalarReturnsNullWhenNoValue() { $client = $this->createMock(TestClient::class); $client->method('search')->willReturn(new ArrayResponse([ diff --git a/tests/ParamsTest.php b/tests/ParamsTest.php index e26e5f9..bb22f33 100644 --- a/tests/ParamsTest.php +++ b/tests/ParamsTest.php @@ -317,6 +317,26 @@ public function testIndicesBoost() $this->assertQuery($expectedJson, $query); } + public function testIndicesBoostChained() + { +$expectedJson = <<matchAll(); + $query->indicesBoost(['index-1' => 1.4]) + ->indicesBoost(['index-2' => 1.2]); + $this->assertQuery($expectedJson, $query); + } + public function testChainingParamsAndQuery() { $expectedJson = <<spanOr(function (SpanOr $spanOr) { - $spanOr->addClause(function (Query $q) { + $spanOr->clauses(function (Query $q) { $q->spanTerm('field1', 'bar'); }); - $spanOr->addClause(function (Query $q) { + $spanOr->clauses(function (Query $q) { $q->spanTerm('field2', 'baz'); }); }); @@ -340,10 +340,10 @@ public function testSpanNearAddClause() JSON; $query = new Query(); $query->spanNear(function (SpanNear $spanNear) { - $spanNear->addClause(function (Query $q) { + $spanNear->clauses(function (Query $q) { $q->spanTerm('field1', 'bar'); }); - $spanNear->addClause(function (Query $q) { + $spanNear->clauses(function (Query $q) { $q->spanTerm('field2', 'baz'); }); $spanNear->slop(5)->inOrder(true);