cloud-castle / elastic-search
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.
Requires
- php: >=8.1
Requires (Dev)
- deptrac/deptrac: ^3.0 || ^4.0
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.75
- icanhazstring/composer-unused: ^0.9
- infection/infection: ^0.29 || ^0.33
- php-parallel-lint/php-parallel-lint: ^1.4
- phpmd/phpmd: ^2.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12 || ^2.1
- phpstan/phpstan-deprecation-rules: ^1.2 || ^2.0
- phpstan/phpstan-phpunit: ^1.4 || ^2.0
- phpstan/phpstan-strict-rules: ^1.6 || ^2.0
- phpunit/phpunit: ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19 || ^0.20
- rector/rector: ^1.2 || ^2.0
- roave/security-advisories: dev-latest
- squizlabs/php_codesniffer: ^3.12 || ^4.0
- vimeo/psalm: ^6.0
- webmozart/assert: ^1.11
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 19:22:52 UTC
README
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano
CloudCastle ElasticSearch
Клиент 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. Возможности (объединение фич всех пакетов)
| Возможность | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-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 |
| Всего | 28 | 13 | 11 | 15 | 3 | 12 | 🏆 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)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Проверено PHP-файлов | 369 | 114 | 635 | 230 | 131 | 22 | |
| Синтаксических ошибок | 0 | 0 | 0 | 0 | 0 | 0 | все |
| Итог (побед) | 1 | 1 | 1 | 1 | 1 | 1 | 🏆 CloudCastle, elasticsearch-php, opensearch-php, Elastica, ONGR DSL, elastic-adapter |
3. Psalm (errorLevel 1)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Ошибок | 0 | 2 255 | 6 916 | 1 898 | 834 | 116 | CloudCastle |
| Ошибок без «неиспользуемого кода» | 0 | 1 554 | 6 276 | 1 076 | 752 | 113 | CloudCastle |
| Ошибок на 1000 строк кода | 0 | 187,74 | 246,5 | 201,89 | 178,59 | 125,81 | CloudCastle |
| Итог (побед) | 3 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
Psalm с findUnusedCode считает неиспользуемым публичный API, который не вызывается в анализируемом коде, поэтому есть строка без этих типов. Вместе с src/ анализируются тесты из поставки (у нас, ONGR и elastic-adapter), ошибки считаются только по src/.
4. PHPStan (level max + strict-rules)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Ошибок | 0 | 1 227 | 3 686 | 913 | 1 003 | 282 | CloudCastle |
| Ошибок на 1000 строк кода | 0 | 102,16 | 131,38 | 97,12 | 214,78 | 305,86 | CloudCastle |
| Итог (побед) | 2 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
5. PHPMD
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Нарушений | 0 | 254 | 318 | 204 | 99 | 16 | CloudCastle |
| Нарушений на 1000 строк кода | 0 | 21,15 | 11,33 | 21,7 | 21,2 | 17,35 | CloudCastle |
| Итог (побед) | 2 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
6. PHPCS (PSR-12)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Нарушений | 0 | 25 746 | 2 918 | 410 | 35 | 45 | CloudCastle |
| Нарушений на 1000 строк кода | 0 | 2 143,54 | 104 | 43,61 | 7,49 | 48,81 | CloudCastle |
| Итог (побед) | 2 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
7. Rector (dry-run)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Файлов с предложенными правками | 0 | 88 | 199 | 165 | 124 | 10 | CloudCastle |
| Доля таких файлов, % | 0 | 77,2 | 31,3 | 71,7 | 94,7 | 45,5 | CloudCastle |
| Итог (побед) | 2 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
8. Deptrac (архитектурные слои)
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Нарушений слоёв | 0 | 0 | 0 | 0 | 0 | 0 | все |
| Неохваченных зависимостей | 0 | 0 | 0 | 0 | 0 | 0 | все |
| Итог (побед) | 2 | 2 | 2 | 2 | 2 | 2 | 🏆 CloudCastle, elasticsearch-php, opensearch-php, Elastica, ONGR DSL, elastic-adapter |
9. Мутационное тестирование
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| MSI, % | 100 | нет данных | нет данных | нет данных | нет данных | нет данных | CloudCastle |
| Мутантов в прогоне | 3 092 | нет данных | нет данных | нет данных | нет данных | нет данных | |
| Итог (побед) | 1 | 0 | 0 | 0 | 0 | 0 | 🏆 CloudCastle |
MSI — из отчёта Infection пакета (2026-10-07). Аналоги не публикуют результаты мутационного тестирования, поэтому «нет данных»; побеждает подтверждённый результат.
10. Безопасность
| Метрика | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-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: уязвимостей нет | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | все |
| Итог (побед) | 10 | 4 | 4 | 3 | 1 | 3 | 🏆 CloudCastle |
Первая строка — прогон tests/Security пакета, у аналогов таких тестов нет. Остальное — свойства по исходникам.
Стандарты безопасности composer-пакетов
| Стандарт | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-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 |
| Всего | 12 | 4 | 4 | 2 | 0 | 2 | 🏆 CloudCastle |
11. Память библиотеки
| Решение | КБ (пик минус baseline) | Относительно лучшего | 🏆 Победитель |
|---|---|---|---|
| PSR-18 + json_*¹ | 414 | ×0,53 | базовый уровень |
| CloudCastle | 778 | ×1,00 | 🏆 |
| elasticsearch-php | 1 391 | ×1,79 | |
| ONGR DSL | 1 619 | ×2,08 | |
| Elastica | 1 854 | ×2,38 | |
| elastic-adapter | 1 975 | ×2,54 | |
| opensearch-php | 2 400 | ×3,08 |
12. Утечки памяти
| Решение | Б прироста на 1000 операций | Относительно лучшего | 🏆 Победитель |
|---|---|---|---|
| CloudCastle | 0 | ×1 | 🏆 |
| elasticsearch-php | 0 | ×1 | 🏆 |
| opensearch-php | 0 | ×1 | 🏆 |
| Elastica | 0 | ×1 | 🏆 |
| ONGR DSL | 0 | ×1 | 🏆 |
| elastic-adapter | 0 | ×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 | базовый уровень |
| CloudCastle | 30,59 | ×1,00 | 🏆 |
| opensearch-php | 32,59 | ×1,07 | |
| elasticsearch-php | 62,15 | ×2,03 | |
| elastic-adapter | 70,99 | ×2,32 | |
| Elastica | 74,77 | ×2,44 | |
| ONGR DSL | 79,64 | ×2,60 |
¹ PSR-18 + json_encode/json_decode вручную — нижняя граница стоимости, а не библиотека; в победители не входит.
14. Качество кода
| Критерий | CloudCastle | elasticsearch-php | opensearch-php | Elastica | ONGR DSL | elastic-adapter | 🏆 Победитель |
|---|---|---|---|---|---|---|---|
| Строк кода в src/ | 9 969 | 12 011 | 28 057 | 9 401 | 4 670 | 922 | |
| PHPDoc у публичных методов, % | 100 | 87,3 | 25,3 | 81,9 | 99,1 | 14,3 | CloudCastle |
| final среди конкретных классов, % | 97,2 | 3 | 0,3 | 0 | 0 | 90 | CloudCastle |
| Файлов с strict_types, % | 100 | 100 | 97,6 | 100 | 0 | 100 | CloudCastle, elasticsearch-php, Elastica, elastic-adapter |
| Средняя цикломатическая сложность метода | 1,54 | 2,96 | 1,63 | 1,46 | 1,34 | 1,55 | ONGR 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 |
| Итог (побед) | 14 | 2 | 2 | 4 | 2 | 4 | 🏆 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.
Документация
- Wiki: https://gitverse.ru/cloud-castle/elastic-search/wiki
- Репозиторий: https://gitverse.ru/cloud-castle/elastic-search
- История изменений: CHANGELOG.md
- Обновление версий: UPGRADING.md
- Как внести вклад: CONTRIBUTING.md
- Кодекс поведения: CODE_OF_CONDUCT.md
- Политика безопасности: SECURITY.md
Лицензия
MIT © CloudCastle (alex-4-17@yandex.ru)
🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano