Search by

cloud-castle / elastic-search

alex-4-17

Full-featured Elasticsearch/OpenSearch client for PHP 8.1+: fluent Query DSL, indices, documents, bulk, aggregations and a built-in in-memory engine for tests.

v0.1.0 2026-07-21 05:47 UTC

This package is auto-updated.

Last update: 2026-10-07 19:22:52 UTC


README

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano

CloudCastle ElasticSearch

CloudCastle ElasticSearch

Packagist Version PHP Version License Downloads Monthly Downloads Stars Dependents Suggesters Security Advisories

CI CodeQL Stars Forks Issues Release Last Commit Contributors

PHPStan PHPMD PHPCS Psalm Coverage Infection MSI OpenSSF Scorecard

Клиент Elasticsearch и OpenSearch для PHP 8.1+. Один клиент для обоих дистрибутивов, документы и индексы, bulk с авторазбиением по числу документов и байтам, поиск с типизированным результатом, объектный Query DSL на все запросы и агрегации, параллельные запросы через curl_multi, scroll и point-in-time генераторами, SQL API с потоковым курсором, запросы на cloud-castle/query-builder и встроенный in-memory движок, чтобы тесты обходились без кластера. Рантайм-зависимости — только PSR и пакеты экосистемы cloud-castle.

Установка

composer require cloud-castle/elastic-search

Нужен PHP 8.1+. HTTP идёт через cloud-castle/http-client (cURL, лимит размера ответа, опциональный запрет приватных сетей), JSON — через cloud-castle/serialize, SQL-запросы строит cloud-castle/query-builder.

Быстрый старт

<?php

use CloudCastle\ElasticSearch\Client;
use CloudCastle\ElasticSearch\Search\SearchBuilder;

$client = new Client('http://localhost:9200');

$client->createIndex('books', [
    'mappings' => ['properties' => ['title' => ['type' => 'text'], 'year' => ['type' => 'integer']]],
]);

$client->index('books', '1', ['title' => 'PHP в действии', 'year' => 2024]);
$client->refresh('books');

$result = $client->search('books', (new SearchBuilder())
    ->query(['match' => ['title' => 'php']])
    ->sort('year', 'desc')
    ->size(10));

foreach ($result->hits() as $hit) {
    echo $hit->documentId(), ': ', $hit->get('title'), PHP_EOL;
}

Клиент принимает DSN, массив параметров, Elastic Cloud ID или объект Config:

use CloudCastle\ElasticSearch\Client;
use CloudCastle\ElasticSearch\Configuration\Config;
use CloudCastle\ElasticSearch\Configuration\Credentials;

$client = new Client('https://user:pass@es.example:9200?timeout=10&prefix=app_');
$client = new Client(['nodes' => ['https://es-1:9200', 'https://es-2:9200'], 'api_key' => '...']);
$client = new Client(Config::fromCloudId('deployment:base64...', new Credentials(apiKey: '...')));

Запросы на query-builder

Условия можно собирать fluent-построителем cloud-castle/query-builder: значения уходят параметрами, склейки ввода в текст запроса нет.

use CloudCastle\QueryBuilder\Query;

$query = Query::select('title', 'year')->from('books')->where('year', '>=', 2020)->orderByDesc('year');

foreach ($client->sqlRows($query) as $row) {      // SQL API, курсор читается генератором
    echo $row['title'], PHP_EOL;
}

$result = $client->searchQuery('books', $query);   // SQL → Query DSL на клиенте → _search
$dsl = $client->translate($query);                 // только трансляция, без поиска

Большие объёмы — генераторами

$client->bulkIndex('books', $generator, chunkSize: 500, idField: 'isbn'); // порции по числу и байтам

foreach ($client->searchAfterHits('books', ['range' => ['year' => ['gte' => 2000]]], pageSize: 500) as $hit) {
    // PIT + search_after: память не растёт с размером выборки
}

Параллельные запросы

$results = $client->batch()
    ->add('books', 'GET', '/books/_count')
    ->add('authors', 'GET', '/authors/_count')
    ->send();                         // один заход через curl_multi, ошибки — по ключам

$pending = $client->requestAsync('GET', '/books/_doc/1');
$client->requestAsync('GET', '/books/_doc/2');
$doc = $pending->wait()->get('_source'); // отправляет оба запроса разом

Тесты без настоящего Elasticsearch

In-memory движок реализует тот же транспорт — код под тестом не отличает его от кластера:

use CloudCastle\ElasticSearch\Client;
use CloudCastle\ElasticSearch\Testing\InMemoryTransport;

$client = new Client(transport: new InMemoryTransport());

$client->index('books', '1', ['title' => 'PHP']);
$client->refresh('books');

self::assertSame(1, $client->count('books'));

Возможности

  • Elasticsearch и OpenSearch — дистрибутив задаётся в конфигурации или определяется по GET /; SQL-пути и диалект выбираются автоматически.
  • Документы — index, get, getMany, exists, update, delete, count, deleteByQuery, updateByQuery, reindex.
  • Индексы — создание и удаление, маппинги, настройки, алиасы и атомарная замена алиаса, шаблоны, open/close, force merge.
  • Bulk — NDJSON, bulkIndex с авторазбиением по числу документов и байтам, сводка BulkResult с ошибками по элементам.
  • Поиск — SearchBuilder (query, post_filter, sort, aggs, highlight, collapse, search_after, PIT), multiSearch, типизированные SearchResult/Hit, маппинг хита в DTO.
  • Query DSL объектами — все запросы (full-text, term-level, compound, geo, span, joining, knn, sparse_vector), bucket-, metric- и pipeline-агрегации, sort, highlight, suggesters, rescore, collapse, inner_hits.
  • Параллельные запросы — batch() и requestAsync(): набор уходит одним заходом через curl_multi, сбойные запросы досылаются с повторами и ротацией узлов.
  • Итерация — scrollHits и searchAfterHits (PIT + search_after) генераторами.
  • SQL API — sql, sqlRows (курсор генератором), translate, searchQuery; запросы из cloud-castle/query-builder или строкой с параметрами.
  • Транспорт — PSR-18, повторы с экспоненциальным бэкоффом, джиттером и ротацией узлов, Basic, API key, Bearer, Elastic Cloud ID, подпись AWS SigV4 (Amazon OpenSearch Service и Serverless), собственные заголовки.
  • Наблюдаемость — OpenTelemetryTransport: span на каждый запрос без тела и текста ошибок.
  • Безопасность — секреты маскируются в дампах и ошибках, редиректы выключены, лимит размера ответа, запрет приватных сетей по желанию, идентификаторы URL-кодируются.
  • In-memory движок — индексы, документы, bulk, поиск (match, term, terms, range, exists, bool), агрегации (terms и метрики), scroll и PIT в памяти.

Сравнение с аналогами

Таблицы сгенерированы автоматически (benchmarks/quality.php, benchmarks/compare.php → benchmarks/generate.php). Замер: PHP 8.1.34 без Xdebug, каждый участник в отдельном процессе, одна и та же операция.

1. Возможности (объединение фич всех пакетов)

ВозможностьCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Любой REST-эндпоинт через request()✅✅✅✅❌❌CloudCastle, elasticsearch-php, opensearch-php, Elastica
Типизированные методы документов, индексов, кластера✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Fluent-построитель поискового запроса✅❌❌✅✅✅CloudCastle, Elastica, ONGR DSL, elastic-adapter
Объектный Query DSL на все типы запросов²✅❌❌✅✅❌CloudCastle, Elastica, ONGR DSL
Агрегации: построение и разбор результата✅❌❌✅✅✅CloudCastle, Elastica, ONGR DSL, elastic-adapter
Типизированный результат (SearchResult, Hit)✅❌❌✅❌✅CloudCastle, Elastica, elastic-adapter
Маппинг хита в DTO✅❌❌❌❌❌CloudCastle
Bulk (NDJSON)✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Авторазбиение bulk по числу документов и байтам✅❌❌❌❌❌CloudCastle
Сводка ошибок bulk✅❌❌✅❌✅CloudCastle, Elastica, elastic-adapter
Итерация scroll✅✅✅✅❌❌CloudCastle, elasticsearch-php, opensearch-php, Elastica
Point-in-time API✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
PIT + search_after потоком (генератор)✅❌❌❌❌❌CloudCastle
SQL API✅✅✅❌❌❌CloudCastle, elasticsearch-php, opensearch-php
Чтение SQL-курсора генератором✅❌❌❌❌❌CloudCastle
Fluent-запрос → Query DSL (cloud-castle/query-builder)✅❌❌❌❌❌CloudCastle
Трансляция построителя в Query DSL на клиенте, в том числе для OpenSearch✅❌❌❌❌❌CloudCastle
Elasticsearch и OpenSearch одним клиентом✅❌❌❌❌❌CloudCastle
Встроенный in-memory движок для тестов✅❌❌❌❌❌CloudCastle
Сменный PSR-18 транспорт✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Повторы с бэкоффом и ротацией узлов✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Elastic Cloud ID✅✅❌✅❌✅CloudCastle, elasticsearch-php, Elastica, elastic-adapter
Basic-аутентификация✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
API key и Bearer-токены✅✅❌✅❌✅CloudCastle, elasticsearch-php, Elastica, elastic-adapter
Подпись AWS SigV4 (Amazon OpenSearch Service)✅❌✅❌❌❌CloudCastle, opensearch-php
Асинхронные запросы³✅✅✅❌❌❌CloudCastle, elasticsearch-php, opensearch-php
Трассировка OpenTelemetry✅✅❌❌❌❌CloudCastle, elasticsearch-php
Нет рантайм-зависимостей вне PSR и своей экосистемы✅❌❌❌❌❌CloudCastle
Всего28131115312🏆 CloudCastle

² Объектный DSL (CloudCastle\ElasticSearch\Dsl) покрывает объединение классов Elastica и ONGR: все запросы, включая span, geo, join, knn и sparse_vector, все bucket-, metric- и pipeline-агрегации, сортировки, highlight, suggesters, rescore, collapse, inner_hits. Дополнительно cloud-castle/query-builder транслирует WHERE/ORDER BY/LIMIT в Query DSL на клиенте, в том числе для OpenSearch.

³ Client::requestAsync() возвращает отложенный ответ, Client::batch() — набор запросов с ключами; сетевой транспорт отправляет их одним параллельным заходом через curl_multi (cloud-castle/curl), повторяя сбойные с ротацией узлов. У elasticsearch-php и opensearch-php асинхронность — промисы HTTPlug поверх подключаемого асинхронного клиента.

Статанализ: наши конфиги (psalm.xml, phpstan.neon, phpmd.xml, phpcs.xml.dist, rector.php, deptrac.yaml) с подменой путей прогоняются по src/ каждого участника. Версии: parallel-lint 1.4.0, psalm 6.16.1, phpstan 2.2.7, phpmd 2.15.0, phpcs 4.0.4, rector 2.5.9, deptrac 4.4.0. Меньше ошибок — лучше; итог — число строк, где участник победил.

2. Синтаксис (php-parallel-lint)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Проверено PHP-файлов36911463523013122
Синтаксических ошибок000000все
Итог (побед)111111🏆 CloudCastle, elasticsearch-php, opensearch-php, Elastica, ONGR DSL, elastic-adapter

3. Psalm (errorLevel 1)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Ошибок02 2556 9161 898834116CloudCastle
Ошибок без «неиспользуемого кода»01 5546 2761 076752113CloudCastle
Ошибок на 1000 строк кода0187,74246,5201,89178,59125,81CloudCastle
Итог (побед)300000🏆 CloudCastle

Psalm с findUnusedCode считает неиспользуемым публичный API, который не вызывается в анализируемом коде, поэтому есть строка без этих типов. Вместе с src/ анализируются тесты из поставки (у нас, ONGR и elastic-adapter), ошибки считаются только по src/.

4. PHPStan (level max + strict-rules)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Ошибок01 2273 6869131 003282CloudCastle
Ошибок на 1000 строк кода0102,16131,3897,12214,78305,86CloudCastle
Итог (побед)200000🏆 CloudCastle

5. PHPMD

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Нарушений02543182049916CloudCastle
Нарушений на 1000 строк кода021,1511,3321,721,217,35CloudCastle
Итог (побед)200000🏆 CloudCastle

6. PHPCS (PSR-12)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Нарушений025 7462 9184103545CloudCastle
Нарушений на 1000 строк кода02 143,5410443,617,4948,81CloudCastle
Итог (побед)200000🏆 CloudCastle

7. Rector (dry-run)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Файлов с предложенными правками08819916512410CloudCastle
Доля таких файлов, %077,231,371,794,745,5CloudCastle
Итог (побед)200000🏆 CloudCastle

8. Deptrac (архитектурные слои)

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Нарушений слоёв000000все
Неохваченных зависимостей000000все
Итог (побед)222222🏆 CloudCastle, elasticsearch-php, opensearch-php, Elastica, ONGR DSL, elastic-adapter

9. Мутационное тестирование

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
MSI, %100нет данныхнет данныхнет данныхнет данныхнет данныхCloudCastle
Мутантов в прогоне3 092нет данныхнет данныхнет данныхнет данныхнет данных
Итог (побед)100000🏆 CloudCastle

MSI — из отчёта Infection пакета (2026-10-07). Аналоги не публикуют результаты мутационного тестирования, поэтому «нет данных»; побеждает подтверждённый результат.

10. Безопасность

МетрикаCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
tests/Security: пройдено тестов17/17нет данныхнет данныхнет данныхнет данныхнет данныхCloudCastle
Опциональный запрет приватных сетей (SSRF)✅❌❌❌❌❌CloudCastle
Редиректы выключены по умолчанию (учётные данные не уходят)✅❌❌❌❌❌CloudCastle
Лимит размера ответа✅❌❌❌❌❌CloudCastle
Маскирование секретов в var_dump/print_r✅❌❌❌❌❌CloudCastle
URL-кодирование идентификаторов✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Типизированные исключения по HTTP-статусам✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Проверка обязательных параметров до запроса✅✅✅❌❌❌CloudCastle, elasticsearch-php, opensearch-php
Параметризованный SQL из построителя✅❌❌❌❌❌CloudCastle
composer audit: уязвимостей нет✅✅✅✅✅✅все
Итог (побед)1044313🏆 CloudCastle

Первая строка — прогон tests/Security пакета, у аналогов таких тестов нет. Остальное — свойства по исходникам.

Стандарты безопасности composer-пакетов

СтандартCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
SECURITY.md (политика раскрытия)✅✅✅❌❌❌CloudCastle, elasticsearch-php, opensearch-php
Security advisories: приватный канал для уязвимостей✅✅✅❌❌❌CloudCastle, elasticsearch-php, opensearch-php
composer audit в CI✅❌❌❌❌❌CloudCastle
roave/security-advisories в dev✅❌❌❌❌❌CloudCastle
OpenSSF Scorecard (проверка в CI)❌❌❌❌❌❌—
OpenSSF Best Practices (бейдж)❌❌❌❌❌❌—
Статанализ в CI (PHPStan / Psalm)✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
Статанализ уровня max (PHPStan + Psalm)✅❌❌❌❌❌CloudCastle
Taint-анализ (psalm --taint-analysis) в CI✅❌❌❌❌❌CloudCastle
CWE-22: URL-кодирование идентификаторов в пути запроса✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
CWE-208: сравнение секретов за постоянное время❌❌❌❌❌❌—
CWE-918 SSRF: запрет приватных адресов✅❌❌❌❌❌CloudCastle
CWE-532/200: секреты не попадают в дампы и ошибки✅❌❌❌❌❌CloudCastle
CWE-400: лимиты ответа и размера bulk✅❌❌❌❌❌CloudCastle
CWE-89: SQL только с параметрами✅❌❌❌❌❌CloudCastle
Всего1244202🏆 CloudCastle

11. Память библиотеки

РешениеКБ (пик минус baseline)Относительно лучшего🏆 Победитель
PSR-18 + json_*¹414×0,53базовый уровень
CloudCastle778×1,00🏆
elasticsearch-php1 391×1,79
ONGR DSL1 619×2,08
Elastica1 854×2,38
elastic-adapter1 975×2,54
opensearch-php2 400×3,08

12. Утечки памяти

РешениеБ прироста на 1000 операцийОтносительно лучшего🏆 Победитель
CloudCastle0×1🏆
elasticsearch-php0×1🏆
opensearch-php0×1🏆
Elastica0×1🏆
ONGR DSL0×1🏆
elastic-adapter0×1🏆
PSR-18 + json_*¹0×1базовый уровень

13. Производительность

_Операция: поиск в индексе с bool-запросом (2 must + 2 filter, сортировка, size 10) через подменный PSR-18 клиент с ответом на 10 хитов и чтение _source каждого хита. Сеть исключена, в замер входят сборка запроса, JSON, транспорт и разбор ответа._

Решениемкс на операциюОтносительно лучшего🏆 Победитель
PSR-18 + json_*¹25,60×0,84базовый уровень
CloudCastle30,59×1,00🏆
opensearch-php32,59×1,07
elasticsearch-php62,15×2,03
elastic-adapter70,99×2,32
Elastica74,77×2,44
ONGR DSL79,64×2,60

¹ PSR-18 + json_encode/json_decode вручную — нижняя граница стоимости, а не библиотека; в победители не входит.

14. Качество кода

КритерийCloudCastleelasticsearch-phpopensearch-phpElasticaONGR DSLelastic-adapter🏆 Победитель
Строк кода в src/9 96912 01128 0579 4014 670922
PHPDoc у публичных методов, %10087,325,381,999,114,3CloudCastle
final среди конкретных классов, %97,230,30090CloudCastle
Файлов с strict_types, %10010097,61000100CloudCastle, elasticsearch-php, Elastica, elastic-adapter
Средняя цикломатическая сложность метода1,542,961,631,461,341,55ONGR DSL
php-parallel-lint✅❌❌❌❌❌CloudCastle
Psalm errorLevel 1✅❌❌❌❌❌CloudCastle
PHPStan✅✅✅✅❌✅CloudCastle, elasticsearch-php, opensearch-php, Elastica, elastic-adapter
PHPStan max + strict-rules без baseline✅❌❌❌❌❌CloudCastle
PHPMD✅❌❌❌❌❌CloudCastle
Стиль кода (PHPCS / PHP-CS-Fixer)✅❌✅❌✅✅CloudCastle, opensearch-php, ONGR DSL, elastic-adapter
Rector✅❌❌✅❌❌CloudCastle, Elastica
Deptrac (архитектурные слои)✅❌❌❌❌❌CloudCastle
Мутационное тестирование, MSI 100%✅❌❌❌❌❌CloudCastle
Покрытие строк 100% на каждый файл✅❌❌❌❌❌CloudCastle
declare(strict_types=1) во всех файлах✅❌❌✅❌✅CloudCastle, Elastica, elastic-adapter
Итог (побед)1422424🏆 CloudCastle

Плюсы, минусы и когда применять

Сильные стороны

  • Быстрее аналогов на одинаковом поиске: 31 мкс против 33 у opensearch-php, 62 у elasticsearch-php и 71–80 у elastic-adapter, Elastica и ONGR DSL.
  • Меньше всех памяти: около 0,8 МБ на библиотеку против 1,4–2,4 МБ у аналогов.
  • Функционал — объединение возможностей всех пяти аналогов: объектный DSL на все запросы и агрегации, как у Elastica и ONGR, SQL и асинхронность, как у официальных клиентов, SigV4, как у opensearch-php.
  • Возможности, которых нет ни у одного аналога: один клиент для Elasticsearch и OpenSearch, авторазбиение bulk по байтам, PIT и SQL-курсор генераторами, запросы на query-builder с трансляцией в Query DSL, in-memory движок для тестов.
  • Безопасные умолчания транспорта: без редиректов, с лимитом ответа, без секретов в дампах; taint-анализ и composer audit в CI.
  • Качество: PHPStan max + strict, Psalm 1, PHPMD, Deptrac, покрытие и MSI 100%.

Минусы

  • Пакет моложе и пока менее распространён, чем официальный клиент и Elastica: меньше ответов на Stack Overflow и готовых интеграций с фреймворками.

Когда применять

  • Сервисы и приложения на PHP 8.1+, где важны скорость клиента, память и чистые зависимости.
  • Проекты, которые работают и с Elasticsearch, и с OpenSearch или переезжают между ними, в том числе Amazon OpenSearch Service (SigV4).
  • Импорт, переиндексация и выгрузки больших объёмов: bulk порциями, PIT и курсоры генераторами.
  • Дашборды и страницы, которым нужно несколько независимых запросов: batch() отправит их параллельно.
  • Отчёты и админки, где запросы удобнее собирать построителем, а не массивами DSL.
  • Модульные тесты поиска без поднятого кластера — in-memory движок.

Рекомендации

  • In-memory движок рассчитан на модульные тесты и покрывает частое подмножество Query DSL; сценарии с анализаторами, плагинами и скриптами проверяйте интеграционными тестами на настоящем кластере.
  • Если в компании обязателен клиент от вендора с коммерческой поддержкой, берите elasticsearch-php или opensearch-php: по возможностям этот пакет их перекрывает, но за ним нет вендора.

Разработка

composer install
composer check    # линтеры + статический анализ + тесты
composer fix      # автоисправления (Rector, PHP CS Fixer, PHPCBF)
composer ci       # полный CI-пайплайн локально
php benchmarks/compare.php --setup && php benchmarks/generate.php   # сравнение с аналогами

Полный список команд с описаниями: composer run-script --list.

Документация

Лицензия

MIT © CloudCastle (alex-4-17@yandex.ru)

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano