DE-159650: Upgrade elasticsearch/elasticsearch from Elasticsearch v7.1.1 to Elasticsearch v9.3.0 - #28
Conversation
…ing updates Move putMapping logic into Elastica\Client::putIndexMapping() so that Mapping::send() no longer traverses the Connection::getClient() chain which returns the final Elastic\Elasticsearch\Client — a class that cannot be mocked in unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Reference/testing PR that consolidates the full migration from legacy elasticsearch/elasticsearch v7 endpoint objects to the native elasticsearch-php v9 client API, alongside the supporting infra/test updates needed to run against ES 9.x.
Changes:
- Upgrades runtime/infra to ES 9.x + PHP 8.1 and updates PHPUnit config.
- Replaces
Elasticsearch\Endpoints\*usage acrosssrc/with native v9 client calls. - Updates (and in several cases skips/relaxes) functional tests to align with ES v9 response/behavior changes.
Reviewed changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/Transport/NullTransportTest.php | Skips NullTransport functional coverage |
| tests/Transport/HttpTest.php | Skips proxy/body reuse/compression tests |
| tests/Transport/GuzzleTest.php | Skips proxy/body reuse tests; guzzle check |
| tests/StatusTest.php | Broaden exception handling for ES v9 |
| tests/SnapshotTest.php | Snapshot test tweaks for ES v9 |
| tests/ResultTest.php | Sort expectations updated for ES v9 |
| tests/ResultSetTest.php | Adjust invalid offset tests + skipping |
| tests/ResultSet/ProcessingBuilderTest.php | Threads ApiVersion through builders |
| tests/ResultSet/ChainProcessorTest.php | Threads ApiVersion through ResultSet |
| tests/ResultSet/BuilderTest.php | DefaultBuilder now requires ApiVersion |
| tests/ResponseFunctionalTest.php | Skips removed type-based mapping behavior |
| tests/ReindexTest.php | Handles v9 client exceptions in assertions |
| tests/Query/RangeTest.php | Uses gte/lte for range query |
| tests/Query/FunctionScoreTest.php | Random score field changes for ES v9 |
| tests/Query/CommonTest.php | Skips removed “common” query |
| tests/Processor/AttachmentProcessorTest.php | Plugin presence check in setUp |
| tests/PipelineTest.php | Handles v9 error response structure |
| tests/Node/InfoTest.php | Skips ingest-attachment assertion if absent |
| tests/Multi/SearchTest.php | Error-handling + search options tweaks |
| tests/Multi/MultiBuilderTest.php | Adds ApiVersion argument threading |
| tests/MappingTest.php | Mapping payload updated for ES v9 |
| tests/IndexTest.php | Replaces endpoint usage with native client |
| tests/Index/SettingsTest.php | Handles v9 exception formats; cleanup |
| tests/Exception/ResponseExceptionTest.php | Adapts expected error types for v9 |
| tests/Exception/PartialShardFailureExceptionTest.php | Builder now requires ApiVersion |
| tests/DocumentTest.php | PHPUnit assertion API update |
| tests/ConnectionTest.php | Transport factory now requires logger args |
| tests/Cluster/SettingsTest.php | Cluster settings updated + v9 exceptions |
| tests/ClientFunctionalTest.php | Uses native client for endpoint-like tests |
| tests/BulkTest.php | Updates bulk update action creation for v9 |
| tests/Bulk/Action/UpdateDocumentTest.php | Uses AbstractDocument::create w/ ApiVersion |
| tests/Bulk/Action/AbstractDocumentTest.php | AbstractDocument::create signature updates |
| tests/Base.php | Functional teardown + ingest pipeline via v9 client |
| tests/Aggregation/PercentilesTest.php | Looser assertions for percentile variability |
| tests/Aggregation/GeoBoundsTest.php | Delta assertions for float values |
| src/Task.php | Tasks API migrated to native client |
| src/Status.php | Alias/stats calls migrated to native client |
| src/Snapshot.php | Snapshot restore migrated to native client |
| src/Reindex.php | Reindex API migrated + param whitelist |
| src/Pipeline.php | Ingest pipeline APIs migrated to native client |
| src/Node/Stats.php | Node stats via native client |
| src/Node/Info.php | Node info via native client |
| src/Mapping.php | Mapping updates routed via Client helper |
| src/Index/Stats.php | Index stats via native client |
| src/Index/Recovery.php | Index recovery via native client |
| src/Index.php | Large-scale migration to native indices/docs APIs |
| src/Elasticsearch/Endpoints/Update.php | Removes legacy endpoint shim |
| src/ElasticSearchVersion.php | Adds VERSION_9 constant |
| src/Connection.php | Adds cached v9 client builder on Connection |
| src/Cluster/Health.php | Cluster health via native client |
| src/Cluster.php | Cluster state via native client |
| src/Client.php | Native client usage + logging tweaks + new helper |
| src/Bulk.php | Bulk response construction updated |
| src/ApiVersion.php | Adds API_VERSION_9 constant |
| phpunit.xml.dist | Updates schema + coverage config |
| phpstan-baseline.neon | Removes baseline entries resolved by refactor |
| docker/php/Dockerfile | PHP base image bumped to 8.1 |
| docker/docker-compose.es.yml | ES cluster updated to 9.3.0 + settings |
| composer.json | Requires PHP 8.1 + elasticsearch v9.3 |
Comments suppressed due to low confidence (9)
src/Index.php:248
- $tags is extracted from $options but never used. This is dead code now that getDocument() bypasses Elastica\Client::request() (which is where tags are used for logging). Either remove CustomOptions::REQUEST_TAGS handling here, or re-route this call through Elastica\Client::request() so tags still have an effect.
$tags = $options[CustomOptions::REQUEST_TAGS] ?? [];
unset($options[CustomOptions::REQUEST_TAGS]);
$params = \array_merge($options, [
'index' => $this->getName(),
'id' => $id,
]);
src/Index.php:500
- $tags is extracted from $options but never used in create(). This suggests request tagging/logging support was lost when moving away from requestEndpoint(). Either remove tag extraction or ensure tags still influence the request/logging path (e.g., by performing the request via Elastica\Client::request()).
$tags = $options[CustomOptions::REQUEST_TAGS] ?? [];
unset($options[CustomOptions::REQUEST_TAGS]);
$allowedOptions = [
'master_timeout',
'timeout',
src/Client.php:698
- requestEndpoint() now always throws a RuntimeException. Even with a deprecation notice, this is a runtime-breaking behavior change for any consumers still calling requestEndpoint() (including those who may not be on ES v9 yet). Consider either removing the method in a major-version bump (so callers fail at compile/static analysis time), or keeping a compatibility implementation (e.g., accept a path/method/body shape) while emitting a deprecation warning, rather than throwing unconditionally.
/**
* Makes calls to the elasticsearch server with usage official client Endpoint.
*
* @deprecated This method is deprecated in v9. Use direct client methods instead.
*
* @param string[] $tags
*
* V9 NOTE: AbstractEndpoint class removed in elasticsearch-php v9.
* This method is kept for backward compatibility but throws an exception.
* Each endpoint should now use the client's direct methods.
*/
public function requestEndpoint($endpoint, array $tags = []): Response
{
throw new \RuntimeException('requestEndpoint() is deprecated in Elasticsearch v9. AbstractEndpoint class no longer exists. Use direct client methods like $client->indices()->refresh() instead.');
}
tests/SnapshotTest.php:79
- This assertion is duplicated back-to-back, which doesn't add coverage and makes future edits noisier. Remove the duplicate assertion (or replace it with the intended stronger check, e.g., count/contents validation).
tests/ClientFunctionalTest.php:176 - This test deletes two documents but only verifies that document 1 is gone. If the delete operation regresses for document 2, this test would still pass. Add an assertion (or a second try/catch) that also verifies document 2 cannot be retrieved after deleteDocuments().
tests/Transport/GuzzleTest.php:16 - PHPUnit lifecycle hook is case-sensitive: "setUpBeforeClass" (capital B) is the method PHPUnit calls. With the current method name "setUpbeforeClass", the guzzle dependency check/skip won't run, so this test class may execute even when guzzle isn't installed.
tests/SnapshotTest.php:69 - Catching and swallowing a broad \Exception here can hide legitimate failures (permissions, repository misconfiguration, etc.) and make the test harder to debug. Prefer catching the specific "not found"/"snapshot missing" exception type that is expected when the snapshot doesn't exist, and fail the test for unexpected exceptions.
src/Connection.php:284 - Connection::getClient() builds the host URL by concatenating the configured path verbatim. If Connection::getPath() returns a non-empty value without a leading "/" (e.g. "es"), the resulting host string becomes invalid ("http://host:9200es"). Normalize the path (ensure it is empty or begins with "/") before formatting the host string.
$hosts = [];
$scheme = $this->hasParam('ssl') && $this->getParam('ssl') ? 'https' : 'http';
$host = $this->getHost();
$port = $this->getPort();
$path = $this->getPath();
$hostString = \sprintf('%s://%s:%d%s', $scheme, $host, $port, $path);
$hosts[] = $hostString;
src/Connection.php:303
- The native elasticsearch-php client builder here ignores several existing Connection settings (proxy, timeouts, compression/transport config, and other config options). This appears to be the reason transport/proxy/compression tests are now unconditionally skipped. Consider wiring these Connection options into ClientBuilder (or documenting/removing the unsupported options) so existing Connection APIs like setProxy()/setTimeout() continue to have effect.
$builder = ClientBuilder::create()
->setHosts($hosts)
;
if ($this->hasParam('username') && $this->hasParam('password')) {
$builder->setBasicAuthentication(
$this->getParam('username'),
$this->getParam('password')
);
}
if ($this->hasParam('api_key')) {
$builder->setApiKey($this->getParam('api_key'));
}
$this->_client = $builder->build();
return $this->_client;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
39e99c6 to
5846de4
Compare
- Add `use Closure;` to Client.php, remove `\Closure` return type prefix - Add `use const PHP_EOL;` to BulkTest.php, remove `\PHP_EOL` prefixes - Add `use GuzzleHttp\Exception\RequestException;` to IndexTest.php and ResultSetTest.php - Add `use Exception;` to SnapshotTest.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
a1d827e to
e9cb868
Compare
…4Test Guzzle 7 + PHP 8.2 throws RequestException instead of the more specific ConnectException when HTTPS is used on an HTTP-only server. ConnectException IS a RequestException, so asserting on the parent class covers both cases. Updated all 6 assertInstanceOf calls consistently across the test file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s and fix Http double-slash URI bug
- Fix double-slash URI in Http::exec() when path is empty (baseUri ended
with '/' and requestPath started with '/', producing '//_nodes')
- Add setUp() to HttpTest to clear http_proxy before each test, preventing
stale proxy env from previous failed tests leaking into subsequent tests
- Move putenv('http_proxy=') before parent::tearDown() in HttpTest to ensure
proxy env is cleared before v9 native client cleanup calls run
- Restore testWithEnvironmentalProxy, testWithEnabledEnvironmentalProxy,
testWithProxy, testWithoutProxy in HttpTest and GuzzleTest (proxy container
is available at ports 8000/8001)
- Restore testRequestSuccessWithHttpCompressionEnabled/Disabled in HttpTest:
use $client->request('/_nodes') via Http transport (not index->create()
which routes through v9 native client), move 'curl' to top-level config
where _prepareConnectionParams() places it in connection.config for
_setupCurl() to find
- Restore testExec in NullTransportTest (pure unit test, no connection needed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- composer.json: ^8.1 -> >=8.2 to align with platform-backend minimum - docker/php/Dockerfile: php:8.1-fpm-alpine -> php:8.3-fpm-alpine Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5d3d25f to
fa1d953
Compare
…uleset Remove useless @inheritdoc PHPDoc annotations, unnecessary parentheses around `new` expressions, redundant escape sequences in strings, and trailing comma style corrections — all enforced by the @PhpCsFixer preset already present in .php-cs-fixer.dist.php. No functional code changes in this commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… upgrade php-cs-fixer to PHP 8.2 CI workflow changes: - Use ES 9.3.0 as GitHub Actions service container (replaces docker-compose) - Upgrade CS job from PHP 7.4 + php-cs-fixer 3.8.0 to PHP 8.2 + 3.94.2 (codebase uses PHP 8.0+ syntax incompatible with the old fixer version) - Use ubuntu-latest, actions/checkout@v4, actions/cache@v4, $GITHUB_OUTPUT Test suite compatibility for ES9 single-node cluster: - number_of_replicas 1->0 (replica shards never allocate on single-node) - Only check primary shards in _waitForAllocation, not replicas - Skip proxy/compression transport tests when PROXY_HOST not set - Fix AwsAuthV4Test to accept ConnectException alongside RequestException - ClientFunctionalTest: assertSame index_total 2->1 (no replica writes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e9cd726 to
fe71d40
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 167 out of 167 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
- codecov/codecov-action@v2 → @v4 (v2 deprecated, Node runtime warnings) - usleep(.5 * 1000000) → usleep(500000) (avoid float-to-int in PHP 8.3+) Items 3 & 4 from review already correct: SnapshotTest catches specific NotFoundException|ResponseException (not generic Exception), and the two assertContains calls check different API responses (create vs get). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…st assertions - src/Client.php, src/Index.php: remove NoNodeAvailableException catches added as local workaround when ES was not running; let SDK exceptions propagate naturally - src/Connection.php: make OpenSearch product-check bypass opt-in via bypass_product_check connection param instead of hardcoding for all connections - src/Reindex.php: restore SIZE='size' for backward compat; add MAX_DOCS='max_docs' for ES9; translate SIZE to max_docs in _resolveBodyOptions for ES9 compatibility - tests/Index/SettingsTest: assert explicit replica count instead of tautology - tests/IndexTemplateTest: skip gracefully when ES9 composable templates block legacy template creation - tests/Node/InfoTest: use _nodes instead of _nodes/stats (avoids broken repo exception and is the correct endpoint for name lookups) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap native ES9 client calls in Index::delete(), Index::create(), Pipeline::deletePipeline(), and Reindex::run() with try/catch to convert ClientResponseException/ServerResponseException into Elastica ResponseException - Fix test assertions for ES9 breaking changes: document_parsing_exception, removed boost mapping param, removed Common query (replaced with BoolQuery should clauses + minimum_should_match), from/to range params replaced with gte/lte, _id fielddata disabled, reindex 409 conflict behavior, allowlist/whitelist regex, coordinate deltas, percentile tolerance, percentile keys returned as floats, sort by seq field - Restore cosmetic-only files from opensearch baseline and apply minimal CS Fixer 3.94.2 formatting - Disable non-ES9 cosmetic CS rules in .php-cs-fixer.dist.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dc46f6f to
a9abab4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 66 out of 66 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Default bypass_product_check to true so OpenSearch works out-of-the-box - Remove unused \$apiVersion assignment in Bulk::_processResponse() - Remove duplicate native_constant_invocation rule from CS config - Fix Request context in Pipeline::deletePipeline() catch (DELETE method + full path) - Fix Request context in Reindex::run() catch (POST method + body) - Add array|string type to Client::__construct \$config parameter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b36ade7 to
59b79a9
Compare
The CS style fix in d568cf4 accidentally removed VERSION_9 = 9 while reformatting. This constant is required for cluster configuration validation and for ClientFactory to build the correct API version for Elasticsearch 9.x connections. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ES9 PHP SDK client in getClient() was only checking the 'ssl' param to determine the HTTP scheme. However, ECS environments configure clusters with 'transport: https' (without 'ssl: true'), causing HTTP requests to be sent to HTTPS endpoints (nginx returns 400). Now also checks strtolower(getTransport()) === 'https' so that clusters with transport: https get the correct https:// scheme in the SDK client URL. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Description: Upgrade elasticsearch/elasticsearch from v7.1.1 to v9.3.0 with PHP ≥8.2 requirement
Possible impact: Elasticsearch index operations, document operations, bulk indexing, search, cluster/snapshot/pipeline APIs, transport layer, CI pipeline
Elasticsearch v7 → v9.3 Migration
Major version upgrade of
BrandEmbassy/Elasticafromelasticsearch/elasticsearch ^7.1.1to^9.3.Why
ES v9 dropped the legacy typed
\Elasticsearch\Clientfacade and its type-based document APIs entirely. The existing Elastica layer built on^7could not communicate with an ES v9 cluster. This migration eliminates all v7 compatibility shims and moves to the officially supportedelastic/elasticsearch ^9.3SDK with full ES v9 REST API compliance.Architecture: Dual-Client Approach
ClientandIndexnow operate with a dual-client approach:elastic/elasticsearch ^9.3): Used for all index-management operations —indices()->create(),indices()->putMapping(),indices()->delete(), stats, settings, reindex, snapshot, pipeline, cluster, node APIsConnectionwas extended to hold and expose the native client instance.Client::putIndexMapping()was added as a dedicated testable bridge for mapping updates.Native client calls in
Index::delete(),Index::create(),Pipeline::deletePipeline(), andReindex::run()are wrapped intry/catchblocks that convertClientResponseException/ServerResponseExceptioninto Elastica's ownResponseException— preserving the existing exception contract for all callers.OpenSearch Compatibility
By default the native client middleware injects
X-Elastic-Product: Elasticsearchinto every response so OpenSearch clusters (which do not send this header) pass the elasticsearch-php v9 product check. Setbypass_product_check: falsein connection params to disable this when strict product verification is required.What Changed (66 files)
AbstractEndpoint; all API calls rewritten using native v9 typed methods^7.2|^8.0to>=8.2ubuntu-20.04runner retired,actions/cache@v2blocked, php-cs-fixer 3.8.0 cannot parse PHP 8.x syntaxDependency Updates (
composer.json)elasticsearch/elasticsearch:^7.1.1→^9.3php:^7.2 || ^8.0→>=8.2(PHP 8.1 is EOL since Dec 2024)psr/log:^1.0 || ^2.0 || ^3.0→^2.0 || ^3.0"allow-plugins": { "php-http/discovery": true }CI (
continuous-integration.yaml)7.15.2→9.3.0(GitHub Actions service container)7.4+ php-cs-fixer3.8.0→ PHP8.2+3.94.2checkout@v2→@v4,cache@v2→@v4,::set-output→$GITHUB_OUTPUT.php-cs-fixer.dist.phpRules disabled to prevent cosmetic churn on files not part of the ES9 migration:
global_namespace_import,trailing_comma_in_multiline,fully_qualified_strict_types,phpdoc_separation,new_with_parentheses,escape_implicit_backslashes,declare_strict_types,no_unneeded_control_parentheses,string_implicit_backslashes,phpdoc_no_alias_tag,yoda_style,curly_braces_position,braces_position,no_extra_blank_lines,blank_line_after_opening_tag,native_function_invocation,phpdoc_order,cast_spaces,native_constant_invocation,single_line_throw,declare_parentheses,class_attributes_separation,linebreak_after_opening_tag,declare_equal_normalize,single_quote,ordered_class_elements,blank_line_between_import_groupsBreaking Changes
_typein requests/responses)include_type_nameparameter removedsrc/Elasticsearch/Endpoints/Update.phpremoved (AbstractEndpointno longer exists in ES9 SDK)requestEndpoint()onClient,Index, andPipelinenow throwsRuntimeException— ES9 SDK removedAbstractEndpoint; use direct client methods insteadKey Source Changes
src/Connection.phpgetClient()returning native ES9ElasticsearchClient; OpenSearch product-check middleware (on by default)src/Client.phpputIndexMapping()added;array|stringtype on$configsrc/Index.phpcreate(),delete()use native client with exception wrappingsrc/Pipeline.phpdeletePipeline()uses native client with correctDELETErequest contextsrc/Reindex.phprun()uses native client with correctPOSTrequest context and bodysrc/Bulk.php$apiVersionparam fromBulkResponseconstructor; removed unused assignmentsrc/Transport/Http.phpKey Test Changes
tests/Exception/ResponseExceptionTest.phpmapper_parsing_exception→document_parsing_exceptiontests/MappingTest.phpboostmapping param (removed in ES9)tests/Query/CommonTest.phpCommonquery removed — replaced withBoolQuery+should+minimum_should_matchtests/Query/RangeTest.phpfrom/toparams →gte/ltetests/Query/FunctionScoreTest.php_idfielddata disabled — seed field changed topricetests/ReindexTest.phpallowlist/whitelistregextests/ResponseFunctionalTest.phptests/ResultTest.phpseqfield instead of_idtests/Aggregation/GeoBoundsTest.phpassertEqualsWithDeltafor floating-point coordinatestests/Aggregation/PercentilesTest.phptests/Multi/SearchTest.phpterminate_aftermoved to query param; assert on result countTest Plan
OK (565 tests, 2582 assertions)on PHP 8.2 and 8.3🤖 Generated with Claude Code