Skip to content

Commit 9b84c7d

Browse files
authored
Merge pull request #12 from olekjs/1.9.0
1.9.0 release
2 parents 6d40f3c + 66b7b7b commit 9b84c7d

File tree

9 files changed

+275
-1
lines changed

9 files changed

+275
-1
lines changed

src/Aggregation/Aggregation.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Olekjs\Elasticsearch\Aggregation;
4+
5+
use Olekjs\Elasticsearch\Contracts\AggregationInterface;
6+
7+
class Aggregation implements AggregationInterface
8+
{
9+
public function __construct(
10+
private readonly string $name,
11+
private readonly array $data,
12+
13+
/** @var AggregationInterface[] $subAggregations */
14+
private array $subAggregations = [],
15+
) {
16+
}
17+
18+
public function getName(): string
19+
{
20+
return $this->name;
21+
}
22+
23+
public function getData(): array
24+
{
25+
return $this->data;
26+
}
27+
28+
public function getSubAggregations(): array
29+
{
30+
return $this->subAggregations;
31+
}
32+
33+
public function addSubAggregation(AggregationInterface $subAggregation): self
34+
{
35+
$this->subAggregations[] = $subAggregation;
36+
37+
return $this;
38+
}
39+
40+
public function toRequestArray(): array
41+
{
42+
$request = [
43+
$this->name => $this->data,
44+
];
45+
46+
if (!empty($this->subAggregations)) {
47+
foreach ($this->subAggregations as $subAggregation) {
48+
$request[$this->name]['aggs'][$subAggregation->getName()] = $this->handleSubAggregationData($subAggregation);
49+
}
50+
}
51+
52+
return $request;
53+
}
54+
55+
private function handleSubAggregationData(Aggregation $aggregation): array
56+
{
57+
$request = $aggregation->getData();
58+
59+
if (!empty($aggregation->subAggregations)) {
60+
foreach ($aggregation->subAggregations as $subAggregation) {
61+
$subAggregationData = $subAggregation->getData();
62+
63+
$request['aggs'][$subAggregation->getName()] = $subAggregationData;
64+
65+
if (!empty($subAggregation->getSubAggregations())) {
66+
$subAggregationData['aggs'] = $this->handleSubAggregationData($subAggregation);
67+
}
68+
}
69+
}
70+
71+
return $request;
72+
}
73+
}

src/Builder/Builder.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Support\Traits\Conditionable;
66
use LogicException;
77
use Olekjs\Elasticsearch\Client;
8+
use Olekjs\Elasticsearch\Contracts\AggregationInterface;
89
use Olekjs\Elasticsearch\Contracts\BuilderInterface;
910
use Olekjs\Elasticsearch\Contracts\BulkOperationInterface;
1011
use Olekjs\Elasticsearch\Contracts\ClientInterface;
@@ -49,6 +50,9 @@ class Builder implements BuilderInterface
4950

5051
private array $select;
5152

53+
/** @var AggregationInterface[] $aggregations */
54+
private array $aggregations;
55+
5256
private array $body = [];
5357

5458
public function __construct(private readonly ClientInterface $client)
@@ -222,6 +226,13 @@ public function orderBy(string $field, string $direction = self::ORDER_DESC, ?st
222226
return $this;
223227
}
224228

229+
public function withAggregation(AggregationInterface $aggregation): self
230+
{
231+
$this->aggregations[] = $aggregation;
232+
233+
return $this;
234+
}
235+
225236
public function offset(int $offset): self
226237
{
227238
$this->body['from'] = $offset;
@@ -422,6 +433,12 @@ public function performSearchBody(): void
422433
$this->body['_source'] = $this->select;
423434
}
424435

436+
if (isset($this->aggregations)) {
437+
$this->body['aggs'] = collect($this->aggregations)
438+
->mapWithKeys(fn(AggregationInterface $aggregation): array => $aggregation->toRequestArray())
439+
->toArray();
440+
}
441+
425442
if (empty($this->body)) {
426443
$this->body['query'] = [
427444
'match_all' => (object)[]

src/Client.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ public function count(string $index, array $data = []): int
279279
unset($data['_source']);
280280
}
281281

282+
if (isset($data['aggs'])) {
283+
unset($data['aggs']);
284+
}
285+
282286
if (empty($data)) {
283287
$data = [
284288
'query' => [
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Olekjs\Elasticsearch\Contracts;
4+
5+
interface AggregationInterface
6+
{
7+
public function getName(): string;
8+
9+
public function getData(): array;
10+
11+
public function getSubAggregations(): array;
12+
13+
public function addSubAggregation(AggregationInterface $subAggregation): self;
14+
15+
public function toRequestArray(): array;
16+
}

src/Contracts/BuilderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public function whereRange(string $field, int|float $value, string $operator): s
6060
*/
6161
public function orderBy(string $field, string $direction = self::ORDER_DESC, ?string $mode = null): self;
6262

63+
public function withAggregation(AggregationInterface $aggregation): self;
64+
6365
public function offset(int $offset): self;
6466

6567
public function limit(int $limit): self;

src/Dto/SearchResponseDto.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Contracts\Support\Arrayable;
66
use Illuminate\Support\Collection;
7+
use Olekjs\Elasticsearch\Contracts\AggregationInterface;
78
use Olekjs\Elasticsearch\Contracts\Collectionable;
89
use Olekjs\Elasticsearch\Contracts\ResponseDtoInterface;
910

@@ -14,6 +15,7 @@ public function __construct(
1415
private readonly bool $isTimedOut,
1516
private readonly ShardsResponseDto $shards,
1617
private readonly SearchHitsDto $result,
18+
private readonly array $aggregations = [],
1719
) {
1820
}
1921

@@ -37,13 +39,19 @@ public function getShards(): ShardsResponseDto
3739
return $this->shards;
3840
}
3941

42+
public function getAggregations(): array
43+
{
44+
return $this->aggregations;
45+
}
46+
4047
public function toArray(): array
4148
{
4249
return [
4350
'took' => $this->getTook(),
4451
'is_timed_out' => $this->getIsTimedOut(),
4552
'shards' => $this->getShards()->toArray(),
4653
'results' => $this->getResult()->toArray(),
54+
'aggregations' => $this->getAggregations(),
4755
];
4856
}
4957

src/Utils/SearchResponse.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public static function from(Response $response, array $data = []): SearchRespons
3434
),
3535
data_get($response, 'hits.hits'),
3636
)
37-
)
37+
),
38+
aggregations: data_get($response, 'aggregations', [])
3839
);
3940
}
4041
}

tests/Unit/AggregationTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Olekjs\Elasticsearch\Tests\Unit;
4+
5+
use Generator;
6+
use Olekjs\Elasticsearch\Aggregation\Aggregation;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class AggregationTest extends TestCase
10+
{
11+
/**
12+
* @dataProvider dataProvider
13+
*/
14+
public function testAggregation(callable $callback, array $expected): void
15+
{
16+
$actual = $callback();
17+
18+
$this->assertSame($expected, $actual);
19+
}
20+
21+
public static function dataProvider(): Generator
22+
{
23+
yield [
24+
function () {
25+
$aggregation = new Aggregation('category-aggregation', [
26+
'terms' => ['field' => 'category']
27+
]);
28+
29+
return $aggregation->toRequestArray();
30+
},
31+
[
32+
'category-aggregation' => [
33+
'terms' => [
34+
'field' => 'category',
35+
]
36+
]
37+
]
38+
];
39+
yield [
40+
function () {
41+
$aggregation = new Aggregation('price-max-aggregation', [
42+
'max' => ['field' => 'price']
43+
]);
44+
45+
46+
return $aggregation->toRequestArray();
47+
},
48+
[
49+
'price-max-aggregation' => [
50+
'max' => [
51+
'field' => 'price',
52+
]
53+
]
54+
]
55+
];
56+
yield [
57+
function () {
58+
$nestedSubAggregation = new Aggregation('nested-aggregation', [
59+
'terms' => ['field' => 'nested']
60+
]);
61+
62+
$minPriceSubAggregation = new Aggregation('price-min-aggregation', [
63+
'min' => ['field' => 'price']
64+
]);
65+
66+
$maxPriceSubAggregation = new Aggregation('price-max-aggregation', [
67+
'max' => ['field' => 'price']
68+
]);
69+
70+
$maxPriceSubAggregation->addSubAggregation($nestedSubAggregation);
71+
72+
$aggregation = new Aggregation('category-aggregation', [
73+
'terms' => ['field' => 'category']
74+
], [$minPriceSubAggregation, $maxPriceSubAggregation]);
75+
76+
77+
return $aggregation->toRequestArray();
78+
},
79+
[
80+
'category-aggregation' => [
81+
'terms' => [
82+
'field' => 'category',
83+
],
84+
'aggs' => [
85+
'price-min-aggregation' => [
86+
'min' => [
87+
'field' => 'price',
88+
]
89+
],
90+
'price-max-aggregation' => [
91+
'max' => [
92+
'field' => 'price',
93+
],
94+
'aggs' => [
95+
'nested-aggregation' => [
96+
'terms' => [
97+
'field' => 'nested'
98+
]
99+
]
100+
]
101+
],
102+
]
103+
]
104+
]
105+
];
106+
}
107+
}

tests/Unit/BuilderTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Olekjs\Elasticsearch\Tests\Unit;
44

55
use LogicException;
6+
use Olekjs\Elasticsearch\Aggregation\Aggregation;
67
use Olekjs\Elasticsearch\Builder\Builder;
78
use Olekjs\Elasticsearch\Bulk\Bulk;
89
use Olekjs\Elasticsearch\Client;
@@ -559,4 +560,49 @@ public function testSelectMethod(): void
559560

560561
$this->assertSame(['_source' => $expected], $builder->getBody());
561562
}
563+
564+
public function testWithAggregationMethod(): void
565+
{
566+
$categoryAggregation = new Aggregation(
567+
'category-aggregation',
568+
[
569+
'terms' => [
570+
'category' => 'test'
571+
]
572+
]
573+
);
574+
575+
$priceAggregation = new Aggregation(
576+
'price-aggregation',
577+
[
578+
'max' => [
579+
'field' => 'price'
580+
]
581+
]
582+
);
583+
584+
$builder = Builder::query()
585+
->withAggregation($categoryAggregation)
586+
->withAggregation($priceAggregation);
587+
588+
$builder->performSearchBody();
589+
590+
$this->assertSame(
591+
[
592+
'aggs' => [
593+
'category-aggregation' => [
594+
'terms' => [
595+
'category' => 'test'
596+
]
597+
],
598+
'price-aggregation' => [
599+
'max' => [
600+
'field' => 'price'
601+
]
602+
]
603+
]
604+
],
605+
$builder->getBody()
606+
);
607+
}
562608
}

0 commit comments

Comments
 (0)