Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,3 @@ jobs:

- name: PHP-CS-Fixer
run: PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run --diff

- name: PHPMD
run: vendor/bin/phpmd src text phpmd.xml
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/vendor/
.claude/
CLAUDE.local.md
CLAUDE.md
composer.lock
.idea
composer.phar
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [8.0.0] - 2026-06-30

First stable release. The public API is now frozen: public method parameter
names are part of the API (named arguments are supported) and will not change
in 8.x patch/minor releases.

### Changed

- `Doc::refresh()` now accepts `bool` (`true`/`false`) in addition to `string` (`'wait_for'`), matching Elasticsearch's `refresh` parameter.

See the 8.0.0-beta.5 and 8.0.0-beta.4 entries for the full feature set and breaking changes introduced during the beta cycle.

## [8.0.0-beta.5] - 2026-06-28

### Added
Expand Down
67 changes: 0 additions & 67 deletions CLAUDE.md

This file was deleted.

18 changes: 0 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,6 @@ EventDispatcher::listen('search.*', function (Event $e) {

</details>

## Long-lived processes

`Index::setClient()`, `ClientManager`, `EventDispatcher`, and `Pagination` hold **static state**. In a long-lived worker (Swoole, RoadRunner, Laravel Octane) this state persists across requests, so a worker leaks the registered client, event listeners, and pagination resolvers between requests.

Reset them between requests — e.g. in a request-terminated hook:

```php
use ElasticKit\Index\Support\ClientManager;
use ElasticKit\Index\Support\EventDispatcher;
use ElasticKit\Index\Support\Pagination;

ClientManager::reset();
EventDispatcher::reset();
Pagination::reset();
```

PHP-FPM forks a worker per request, so this only affects persistent workers.

## Documentation

- [Guide](docs/guide.md) — an e-commerce order scenario, the full flow from install to production
Expand Down
18 changes: 0 additions & 18 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,6 @@ EventDispatcher::listen('search.*', function (Event $e) {

</details>

## 常驻进程

`Index::setClient()`、`ClientManager`、`EventDispatcher`、`Pagination` 持有**静态状态**。在常驻 worker(Swoole、RoadRunner、Laravel Octane)中,这些状态跨请求保留,worker 会把已注册的客户端、事件监听器、分页解析器泄漏到下一个请求。

请在请求之间重置它们——例如在请求终止钩子里:

```php
use ElasticKit\Index\Support\ClientManager;
use ElasticKit\Index\Support\EventDispatcher;
use ElasticKit\Index\Support\Pagination;

ClientManager::reset();
EventDispatcher::reset();
Pagination::reset();
```

PHP-FPM 每请求 fork 一个 worker,因此本节仅影响常驻 worker。

## 文档

- [实践指南](docs/guide.zh.md)——电商订单场景,从安装到上线的完整流程
Expand Down
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"require-dev": {
"phpunit/phpunit": "^10.5",
"phpstan/phpstan": "^2.1",
"friendsofphp/php-cs-fixer": "^3.9",
"phpmd/phpmd": "^2.15"
"friendsofphp/php-cs-fixer": "^3.9"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ Always validate and filter user input before passing it to DSL methods.
| `forceMerge()` | Force-merge index segments |
| `addAlias($alias)` | Add an alias |
| `removeAlias($alias)` | Remove an alias |
| `swapAlias($alias, $target)` | Swap where an alias points |
| `swapAlias($alias, $fromIndex)` | Swap where an alias points |
| `getAliases()` | Get the index's aliases |

### Event list
Expand Down
2 changes: 1 addition & 1 deletion docs/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ Index::setClient($client);
| `forceMerge()` | 强制合并索引段 |
| `addAlias($alias)` | 添加别名 |
| `removeAlias($alias)` | 移除别名 |
| `swapAlias($alias, $target)` | 切换别名指向 |
| `swapAlias($alias, $fromIndex)` | 切换别名指向 |
| `getAliases()` | 获取索引别名 |

### 事件列表
Expand Down
56 changes: 0 additions & 56 deletions phpmd.xml

This file was deleted.

1 change: 0 additions & 1 deletion src/DSL/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* Abstract base class for DSL nodes (query types, params).
*
* @phpstan-consistent-constructor
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity) the DSL base accumulates many thin accessors
*/
abstract class Node
{
Expand Down
1 change: 0 additions & 1 deletion src/DSL/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
* Query container that combines multiple query conditions into an Elasticsearch DSL query.
*
* @phpstan-consistent-constructor
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class Query extends Node
{
Expand Down
2 changes: 0 additions & 2 deletions src/DSL/Support/RegistersAgg.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ trait RegistersAgg
/**
* Register an aggregation under an alias into the given store.
*
* @SuppressWarnings(PHPMD.NPathComplexity) flat type-dispatch on polymorphic input; each branch is simple
*
* @param mixed $alias aggregation alias (string), Agg instance (when passed as the only argument), or null
* @param mixed $aggs Agg instance, array, closure, or null
* @param array<string, Agg> $store the target aggregation store (by reference)
Expand Down
8 changes: 4 additions & 4 deletions src/Index/Doc.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ class Doc
private int $retryOnConflict = 0;

/**
* @var string|null
* @var string|bool|null
*/
private ?string $refresh = null;
private string|bool|null $refresh = null;

/**
* @param string|int|null $id document id, or null/'' to let ES auto-generate
Expand Down Expand Up @@ -59,10 +59,10 @@ public function retryOnConflict(int $count): static
/**
* Set refresh for subsequent write operations (true/false/wait_for).
*
* @param string $value
* @param string|bool $value
* @return $this
*/
public function refresh(string $value): static
public function refresh(string|bool $value): static
{
$this->refresh = $value;

Expand Down
1 change: 0 additions & 1 deletion src/Index/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ public function trackTotalHits(): int|bool
*
* @param array<string, mixed> $context user-defined context passed from rebuild
* @return iterable<string|int, array<string, mixed>>
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function source(array $context = []): iterable
{
Expand Down
1 change: 0 additions & 1 deletion src/Index/Rebuild.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
* $name is always the app-facing name. After rebuild, $name becomes an alias
* pointing to a backing index generated by rebuildName().
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class Rebuild
{
Expand Down
75 changes: 75 additions & 0 deletions tests/Integration/Dsl/ShapeContractTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Tests\Integration\Dsl;

use ElasticKit\DSL\Query;
use ElasticKit\DSL\Queries\Shape\Shape;
use Tests\Integration\IntegrationTestCase;

/**
* Shape query contracts against a real Elasticsearch.
*
* The cartesian `shape` query needs a `shape`-typed field, which the shared
* index does not have (its `shape` field is geo_shape), so each test builds a
* dedicated index with two disjoint envelopes.
*/
class ShapeContractTest extends IntegrationTestCase
{
private ?string $shapeIndex = null;

protected function tearDown(): void
{
if ($this->shapeIndex !== null && static::$esClient !== null) {
try {
static::$esClient->indices()->delete(['index' => $this->shapeIndex]);
} catch (\Throwable $e) {
// best-effort cleanup
}
$this->shapeIndex = null;
}
parent::tearDown();
}

private function withShapeIndex(): string
{
$client = static::$esClient;
$index = 'ek_shape_' . bin2hex(random_bytes(4));
$this->shapeIndex = $index;

$client->indices()->create([
'index' => $index,
'body' => ['mappings' => ['properties' => ['geom' => ['type' => 'shape']]]],
]);
$client->index(['index' => $index, 'id' => '1', 'body' => ['geom' => ['type' => 'envelope', 'coordinates' => [[0, 10], [10, 0]]]]]);
$client->index(['index' => $index, 'id' => '2', 'body' => ['geom' => ['type' => 'envelope', 'coordinates' => [[100, 110], [110, 100]]]]]);
$client->indices()->refresh(['index' => $index]);

return $index;
}

public function testShapeIntersects(): void
{
$index = $this->withShapeIndex();
// query envelope overlaps doc 1's [[0,0],[10,10]] only
$q = (new Query())->shape('geom', function (Shape $shape) {
$shape->shape(['type' => 'envelope', 'coordinates' => [[5, 15], [15, 5]]])
->relation('intersects');
});
$total = static::$esClient->search(['index' => $index, 'body' => $q->toArray()])->asArray()['hits']['total']['value'] ?? 0;
$this->assertSame(1, $total);
}

public function testShapeDisjoint(): void
{
$index = $this->withShapeIndex();
// relation DISJOINT inverts: doc 2 (far) matches instead of doc 1
$q = (new Query())->shape('geom', function (Shape $shape) {
$shape->shape(['type' => 'envelope', 'coordinates' => [[5, 15], [15, 5]]])
->relation('disjoint');
});
$total = static::$esClient->search(['index' => $index, 'body' => $q->toArray()])->asArray()['hits']['total']['value'] ?? 0;
$this->assertSame(1, $total);
}
}
Loading
Loading