Skip to content

Commit d354d05

Browse files
committed
[WIP] Init project files
0 parents  commit d354d05

19 files changed

+652
-0
lines changed

config/services.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
return [
4+
'elasticsearch' => [
5+
'url' => env('ELASTICSEARCH_URL', 'http://localhost:9200'),
6+
],
7+
];

src/Client.php

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
<?php
2+
3+
namespace Elasticsearch;
4+
5+
use Elasticsearch\Contracts\ClientInterface;
6+
use Elasticsearch\Dto\IndexResponseDto;
7+
use Elasticsearch\Dto\SearchHitDto;
8+
use Elasticsearch\Dto\SearchHitsDto;
9+
use Elasticsearch\Dto\SearchResponseDto;
10+
use Elasticsearch\Dto\ShardsResponseDto;
11+
use Elasticsearch\Exceptions\ConflictResponseException;
12+
use Elasticsearch\Exceptions\DeleteResponseException;
13+
use Elasticsearch\Exceptions\FindResponseException;
14+
use Elasticsearch\Exceptions\IndexNotFoundResponseException;
15+
use Elasticsearch\Exceptions\IndexResponseException;
16+
use Elasticsearch\Exceptions\NotFoundResponseException;
17+
use Elasticsearch\Exceptions\SearchResponseException;
18+
use Elasticsearch\Exceptions\UpdateResponseException;
19+
use Illuminate\Support\Facades\Http;
20+
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
21+
use Elasticsearch\Dto\FindResponseDto;
22+
23+
class Client implements ClientInterface
24+
{
25+
/**
26+
* @throws SearchResponseException
27+
*/
28+
public function search(string $index, array $data): SearchResponseDto
29+
{
30+
$response = Http::acceptJson()
31+
->asJson()
32+
->baseUrl(config('services.elasticsearch.url'))
33+
->post("$index/_search", $data);
34+
35+
if ($response->clientError()) {
36+
throw new SearchResponseException(
37+
data_get($response, 'error.reason'),
38+
$response->status(),
39+
);
40+
}
41+
42+
return new SearchResponseDto(
43+
took: data_get($response, 'took'),
44+
isTimedOut: data_get($response, 'timed_out'),
45+
shards: new ShardsResponseDto(
46+
total: data_get($response, '_shards.total'),
47+
successful: data_get($response, '_shards.successful'),
48+
failed: data_get($response, '_shards.failed'),
49+
skipped: data_get($response, '_shards.skipped'),
50+
),
51+
hits: new SearchHitsDto(
52+
total: data_get($response, 'hits.total'),
53+
maxScore: data_get($response, 'hits.max_score'),
54+
hits: array_map(
55+
fn(array $hit): SearchHitDto => new SearchHitDto(
56+
index: data_get($hit, '_index'),
57+
id: data_get($hit, '_id'),
58+
score: data_get($hit, '_score'),
59+
source: data_get($hit, '_source'),
60+
),
61+
data_get($response, 'hits.hits'),
62+
)
63+
)
64+
);
65+
}
66+
67+
/**
68+
* @throws IndexNotFoundResponseException
69+
* @throws FindResponseException
70+
*/
71+
public function find(string $index, string|int $id): ?FindResponseDto
72+
{
73+
$response = Http::acceptJson()
74+
->asJson()
75+
->baseUrl(config('services.elasticsearch.url'))
76+
->get("$index/_doc/$id");
77+
78+
if ($response->notFound() && data_get($response, 'error.type') === 'index_not_found_exception') {
79+
throw new IndexNotFoundResponseException(
80+
data_get($response, 'error.reason'),
81+
$response->status(),
82+
);
83+
}
84+
85+
if ($response->notFound() && !data_get($response, 'found')) {
86+
return null;
87+
}
88+
89+
if ($response->clientError()) {
90+
throw new FindResponseException(
91+
data_get($response, 'error.reason'),
92+
$response->status(),
93+
);
94+
}
95+
96+
return new FindResponseDto(
97+
index: data_get($response, '_index'),
98+
id: data_get($response, '_id'),
99+
version: data_get($response, '_version'),
100+
sequenceNumber: data_get($response, '_seq_no'),
101+
primaryTerm: data_get($response, '_primary_term'),
102+
found: data_get($response, 'found'),
103+
source: data_get($response, '_source'),
104+
);
105+
}
106+
107+
/**
108+
* @throws NotFoundResponseException
109+
* @throws IndexNotFoundResponseException
110+
* @throws FindResponseException
111+
*/
112+
public function findOrFail(string $index, string|int $id): FindResponseDto
113+
{
114+
$result = $this->find($index, $id);
115+
116+
if (is_null($result)) {
117+
throw new NotFoundResponseException(
118+
"Document [$id] in index [$index] not found.",
119+
SymfonyResponse::HTTP_NOT_FOUND
120+
);
121+
}
122+
123+
return $result;
124+
}
125+
126+
/**
127+
* @throws IndexResponseException
128+
*/
129+
public function create(string $index, string|int $id, array $data): IndexResponseDto
130+
{
131+
$response = Http::acceptJson()
132+
->asJson()
133+
->baseUrl(config('services.elasticsearch.url'))
134+
->post("$index/_create/$id", $data);
135+
136+
if ($response->clientError()) {
137+
throw new IndexResponseException(
138+
json_encode($response->json()),
139+
$response->status()
140+
);
141+
}
142+
143+
return new IndexResponseDto(
144+
index: data_get($response, '_index'),
145+
id: data_get($response, '_id'),
146+
version: data_get($response, '_version'),
147+
result: data_get($response, 'result'),
148+
shards: new ShardsResponseDto(
149+
total: data_get($response, '_shards.total'),
150+
successful: data_get($response, '_shards.successful'),
151+
failed: data_get($response, '_shards.failed'),
152+
),
153+
sequenceNumber: data_get($response, '_seq_no'),
154+
primaryTerm: data_get($response, '_primary_term')
155+
);
156+
}
157+
158+
/**
159+
* @throws NotFoundResponseException
160+
* @throws UpdateResponseException
161+
* @throws ConflictResponseException
162+
*/
163+
public function update(
164+
string $index,
165+
string|int $id,
166+
array $data,
167+
?int $primaryTerm = null,
168+
?int $sequenceNumber = null
169+
): IndexResponseDto {
170+
$baseUrl = "$index/_update/$id";
171+
172+
if (!is_null($primaryTerm) && !is_null($sequenceNumber)) {
173+
$strictUrl = http_build_query([
174+
'if_primary_term' => $primaryTerm,
175+
'if_seq_no' => $sequenceNumber,
176+
]);
177+
178+
$baseUrl = $baseUrl . '?' . $strictUrl;
179+
}
180+
181+
$response = Http::acceptJson()
182+
->asJson()
183+
->baseUrl(config('services.elasticsearch.url'))
184+
->post($baseUrl, [
185+
'doc' => $data
186+
]);
187+
188+
if ($response->notFound() && data_get($response, 'status') === SymfonyResponse::HTTP_NOT_FOUND) {
189+
throw new NotFoundResponseException(
190+
json_encode($response->json()),
191+
$response->status()
192+
);
193+
}
194+
195+
if (
196+
!is_null($primaryTerm)
197+
&& !is_null($sequenceNumber)
198+
&& $response->clientError()
199+
&& data_get($response, 'status') === SymfonyResponse::HTTP_CONFLICT
200+
) {
201+
throw new ConflictResponseException(
202+
json_encode($response->json()),
203+
$response->status()
204+
);
205+
}
206+
207+
if ($response->clientError()) {
208+
throw new UpdateResponseException(
209+
json_encode($response->json()),
210+
$response->status()
211+
);
212+
}
213+
214+
return new IndexResponseDto(
215+
index: data_get($response, '_index'),
216+
id: data_get($response, '_id'),
217+
version: data_get($response, '_version'),
218+
result: data_get($response, 'result'),
219+
shards: new ShardsResponseDto(
220+
total: data_get($response, '_shards.total'),
221+
successful: data_get($response, '_shards.successful'),
222+
failed: data_get($response, '_shards.failed'),
223+
),
224+
sequenceNumber: data_get($response, '_seq_no'),
225+
primaryTerm: data_get($response, '_primary_term')
226+
);
227+
}
228+
229+
/**
230+
* @throws DeleteResponseException
231+
* @throws NotFoundResponseException
232+
*/
233+
public function delete(string $index, string|int $id): IndexResponseDto
234+
{
235+
$response = Http::acceptJson()
236+
->asJson()
237+
->baseUrl(config('services.elasticsearch.url'))
238+
->delete("$index/_doc/$id");
239+
240+
if ($response->notFound() && data_get($response, 'result') === 'not_found') {
241+
throw new NotFoundResponseException(
242+
json_encode($response->json()),
243+
$response->status()
244+
);
245+
}
246+
247+
if ($response->clientError()) {
248+
throw new DeleteResponseException(
249+
json_encode($response->json()),
250+
$response->status()
251+
);
252+
}
253+
254+
return new IndexResponseDto(
255+
index: data_get($response, '_index'),
256+
id: data_get($response, '_id'),
257+
version: data_get($response, '_version'),
258+
result: data_get($response, 'result'),
259+
shards: new ShardsResponseDto(
260+
total: data_get($response, '_shards.total'),
261+
successful: data_get($response, '_shards.successful'),
262+
failed: data_get($response, '_shards.failed'),
263+
),
264+
sequenceNumber: data_get($response, '_seq_no'),
265+
primaryTerm: data_get($response, '_primary_term')
266+
);
267+
}
268+
}

src/Contracts/ClientInterface.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Elasticsearch\Contracts;
4+
5+
use Elasticsearch\Dto\FindResponseDto;
6+
use Elasticsearch\Dto\IndexResponseDto;
7+
use Elasticsearch\Dto\SearchResponseDto;
8+
9+
interface ClientInterface
10+
{
11+
// todo
12+
// whereIn
13+
// where per field
14+
// increment
15+
// decrement
16+
// update by query ??
17+
// bulk action
18+
19+
public function search(string $index, array $data): SearchResponseDto;
20+
21+
public function find(string $index, string|int $id): ?FindResponseDto;
22+
23+
public function findOrFail(string $index, string|int $id): FindResponseDto;
24+
25+
public function create(string $index, string|int $id, array $data): IndexResponseDto;
26+
27+
public function update(string $index, string|int $id, array $data): IndexResponseDto;
28+
29+
public function delete(string $index, string|int $id): IndexResponseDto;
30+
}

src/Dto/FindResponseDto.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Elasticsearch\Dto;
4+
5+
class FindResponseDto
6+
{
7+
public function __construct(
8+
private readonly string $index,
9+
private readonly string $id,
10+
private readonly int $version,
11+
private readonly int $sequenceNumber,
12+
private readonly int $primaryTerm,
13+
private readonly bool $found,
14+
private readonly array $source,
15+
) {
16+
}
17+
18+
public function getIndex(): ?string
19+
{
20+
return $this->index;
21+
}
22+
23+
public function getId(): string
24+
{
25+
return $this->id;
26+
}
27+
28+
public function getVersion(): int
29+
{
30+
return $this->version;
31+
}
32+
33+
public function getPrimaryTerm(): int
34+
{
35+
return $this->primaryTerm;
36+
}
37+
38+
public function getSequenceNumber(): int
39+
{
40+
return $this->sequenceNumber;
41+
}
42+
43+
public function getSource(): array
44+
{
45+
return $this->source;
46+
}
47+
48+
public function isFound(): bool
49+
{
50+
return $this->found;
51+
}
52+
}

0 commit comments

Comments
 (0)