Bicycle/Services/Elasticsearch/Client.php
Egor Isaev 9d4edf147a dev
2026-08-07 11:40:03 +03:00

153 lines
5.3 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @author Egor Isaev
* @description Client.php
* @copyright (c) 07/08/2026
*/
namespace Services\Elasticsearch;
use RuntimeException;
use System\Classes\HTTP\Client\Curl;
use System\Classes\HTTP\Client\Request as HttpRequest;
use System\Classes\HTTP\Client\Response;
/**
* Тонкая обёртка над Elasticsearch REST API поверх System\Classes\HTTP\Client\Curl —
* без тяжёлой composer-зависимости elasticsearch/elasticsearch, только базовые операции
* (индексация/поиск/чтение/удаление документа, создание/удаление индекса).
*/
class Client
{
/**
* @param array $config base_url, timeout (сек, по умолчанию 10), username/password (Basic auth, опционально)
*/
public function __construct(protected array $config)
{
}
/**
* Индексирует документ. Без $id — Elasticsearch сгенерирует его сам (POST);
* с $id — документ по этому id создаётся/перезаписывается (PUT).
*
* @param string $index Имя индекса
* @param array $document Тело документа
* @param string|null $id ID документа
* @return array Ответ Elasticsearch (декодированный JSON)
* @throws \JsonException
*/
public function index(string $index, array $document, ?string $id = null): array
{
$path = $id !== null ? "/$index/_doc/" . rawurlencode($id) : "/$index/_doc";
$method = $id !== null ? 'PUT' : 'POST';
return $this->request($method, $path, $document)->json();
}
/**
* @param string $index Имя индекса
* @param string $id ID документа
* @return array|null null, если документ не найден (404)
* @throws \JsonException
*/
public function get(string $index, string $id): ?array
{
$response = $this->request('GET', "/$index/_doc/" . rawurlencode($id));
return $response->status() === 404 ? null : $response->json();
}
/**
* @param string $index Имя индекса
* @param string $id ID документа
* @return bool
*/
public function exists(string $index, string $id): bool
{
return $this->request('GET', "/$index/_doc/" . rawurlencode($id))->status() === 200;
}
/**
* @param string $index Имя индекса
* @param array $query Elasticsearch Query DSL, например ['query' => ['match' => ['title' => 'foo']]]
* @return array Ответ Elasticsearch (декодированный JSON)
* @throws \JsonException
*/
public function search(string $index, array $query): array
{
return $this->request('POST', "/$index/_search", $query)->json();
}
/**
* @param string $index Имя индекса
* @param string $id ID документа
* @return bool true, если удалён
*/
public function delete(string $index, string $id): bool
{
return $this->request('DELETE', "/$index/_doc/" . rawurlencode($id))->isSuccess();
}
/**
* @param string $index Имя индекса
* @param array $body settings/mappings (опционально)
* @return array Ответ Elasticsearch (декодированный JSON)
* @throws \JsonException
*/
public function createIndex(string $index, array $body = []): array
{
return $this->request('PUT', "/$index", $body ?: null)->json();
}
/**
* @param string $index Имя индекса
* @return bool true, если удалён
*/
public function deleteIndex(string $index): bool
{
return $this->request('DELETE', "/$index")->isSuccess();
}
/**
* Проверяет доступность кластера. Не бросает исключение при недоступности — просто false
* (удобно для graceful skip в тестах/коде, когда Elasticsearch может быть не поднят).
*
* @return bool
*/
public function ping(): bool
{
try {
return $this->request('GET', '/')->isSuccess();
} catch (RuntimeException) {
return false;
}
}
/**
* @param string $method HTTP-метод
* @param string $path Путь относительно base_url
* @param array|null $body Тело запроса (JSON), null — без тела
* @return Response
*/
protected function request(string $method, string $path, ?array $body = null): Response
{
$http_request = HttpRequest::factory(rtrim($this->config['base_url'] ?? '', '/') . $path)
->method($method)
->timeout($this->config['timeout'] ?? 10);
if ($body !== null) {
$http_request->json($body);
}
if (!empty($this->config['username'])) {
$http_request->header(
'Authorization',
'Basic ' . base64_encode($this->config['username'] . ':' . ($this->config['password'] ?? ''))
);
}
return (new Curl())->execute($http_request);
}
}