diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b4df6def02..68679f137f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -774,6 +774,59 @@ jobs: - name: Run Behat tests run: vendor/bin/behat --out=std --format=progress --profile=elasticsearch --no-interaction + elasticsearch-v7: + name: Behat (PHP ${{ matrix.php }}) (Elasticsearch v7) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - '8.3' + fail-fast: false + env: + APP_ENV: elasticsearch + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Configure sysctl limits + run: | + sudo swapoff -a + sudo sysctl -w vm.swappiness=1 + sudo sysctl -w fs.file-max=262144 + sudo sysctl -w vm.max_map_count=262144 + - name: Runs Elasticsearch + uses: elastic/elastic-github-actions/elasticsearch@master + with: + stack-version: '7.17.0' + security-enabled: false + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring, mongodb + coverage: none + ini-values: memory_limit=-1 + - name: Get composer cache directory + id: composercache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: | + composer global require soyuka/pmu + composer global config allow-plugins.soyuka/pmu true --no-interaction + composer global link . + composer update elasticsearch/elasticsearch --prefer-lowest + - name: Clear test app cache + run: tests/Fixtures/app/console cache:clear --ansi + - name: Run Behat tests + run: vendor/bin/behat --out=std --format=progress --profile=elasticsearch --no-interaction + phpunit-no-deprecations: name: PHPUnit (PHP ${{ matrix.php }}) (no deprecations) runs-on: ubuntu-latest diff --git a/composer.json b/composer.json index 290828ac782..65521de951d 100644 --- a/composer.json +++ b/composer.json @@ -127,7 +127,7 @@ "doctrine/mongodb-odm": "^2.6", "doctrine/mongodb-odm-bundle": "^4.0 || ^5.0", "doctrine/orm": "^2.17 || ^3.0", - "elasticsearch/elasticsearch": "^8.4", + "elasticsearch/elasticsearch": "^7.17 || ^8.4", "friends-of-behat/mink-browserkit-driver": "^1.3.1", "friends-of-behat/mink-extension": "^2.2", "friends-of-behat/symfony-extension": "^2.1", diff --git a/src/Elasticsearch/State/CollectionProvider.php b/src/Elasticsearch/State/CollectionProvider.php index e4ade908821..a366d5449bd 100644 --- a/src/Elasticsearch/State/CollectionProvider.php +++ b/src/Elasticsearch/State/CollectionProvider.php @@ -24,6 +24,8 @@ use Elastic\Elasticsearch\Client; use Elastic\Elasticsearch\Exception\ClientResponseException; use Elastic\Elasticsearch\Response\Elasticsearch; +use Elasticsearch\Client as V7Client; +use Elasticsearch\Common\Exceptions\Missing404Exception as V7Missing404Exception; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -37,7 +39,7 @@ final class CollectionProvider implements ProviderInterface /** * @param RequestBodySearchCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(private readonly Client $client, private readonly ?DenormalizerInterface $denormalizer = null, private readonly ?Pagination $pagination = null, private readonly iterable $collectionExtensions = [], private readonly ?InflectorInterface $inflector = new Inflector()) + public function __construct(private readonly V7Client|Client $client, private readonly ?DenormalizerInterface $denormalizer = null, private readonly ?Pagination $pagination = null, private readonly iterable $collectionExtensions = [], private readonly ?InflectorInterface $inflector = new Inflector()) { } @@ -69,12 +71,14 @@ public function provide(Operation $operation, array $uriVariables = [], array $c try { $documents = $this->client->search($params); + } catch (V7Missing404Exception $e) { + throw new Error(status: $e->getCode(), detail: $e->getMessage(), title: $e->getMessage(), originalTrace: $e->getTrace()); } catch (ClientResponseException $e) { $response = $e->getResponse(); throw new Error(status: $response->getStatusCode(), detail: (string) $response->getBody(), title: $response->getReasonPhrase(), originalTrace: $e->getTrace()); } - if ($documents instanceof Elasticsearch) { + if (class_exists(Elastic\Elasticsearch\Response\Elasticsearch::class) && $documents instanceof Elasticsearch) { $documents = $documents->asArray(); } diff --git a/src/Elasticsearch/State/ItemProvider.php b/src/Elasticsearch/State/ItemProvider.php index 7828145f89d..e77e28bbec8 100644 --- a/src/Elasticsearch/State/ItemProvider.php +++ b/src/Elasticsearch/State/ItemProvider.php @@ -23,6 +23,8 @@ use Elastic\Elasticsearch\Client; use Elastic\Elasticsearch\Exception\ClientResponseException; use Elastic\Elasticsearch\Response\Elasticsearch; +use Elasticsearch\Client as V7Client; +use Elasticsearch\Common\Exceptions\Missing404Exception as V7Missing404Exception; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -34,7 +36,7 @@ */ final class ItemProvider implements ProviderInterface { - public function __construct(private readonly Client $client, private readonly ?DenormalizerInterface $denormalizer = null, private readonly ?InflectorInterface $inflector = new Inflector()) + public function __construct(private readonly V7Client|Client $client, private readonly ?DenormalizerInterface $denormalizer = null, private readonly ?InflectorInterface $inflector = new Inflector()) { } @@ -56,6 +58,8 @@ public function provide(Operation $operation, array $uriVariables = [], array $c try { $document = $this->client->get($params); + } catch (V7Missing404Exception) { + return null; } catch (ClientResponseException $e) { $response = $e->getResponse(); if (404 === $response->getStatusCode()) { @@ -65,7 +69,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c throw new Error(status: $response->getStatusCode(), detail: (string) $response->getBody(), title: $response->getReasonPhrase(), originalTrace: $e->getTrace()); } - if ($document instanceof Elasticsearch) { + if (class_exists(Elastic\Elasticsearch\Response\Elasticsearch::class) && $documents instanceof Elasticsearch) { $document = $document->asArray(); } diff --git a/src/Elasticsearch/composer.json b/src/Elasticsearch/composer.json index 7d4961afef3..3507b2f5411 100644 --- a/src/Elasticsearch/composer.json +++ b/src/Elasticsearch/composer.json @@ -27,7 +27,7 @@ "api-platform/metadata": "^3.4 || ^4.0", "api-platform/serializer": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", - "elasticsearch/elasticsearch": "^8.4", + "elasticsearch/elasticsearch": "^7.17 || ^8.4", "symfony/cache": "^6.4 || ^7.0", "symfony/console": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 4a3f09df3cb..a5fb2fa8f57 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -810,7 +810,11 @@ private function registerElasticsearchConfiguration(ContainerBuilder $container, return; } - $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\Client::class; + $clientClass = class_exists(\Elasticsearch\Client::class) + // ES v8 and up + ? \Elasticsearch\Client::class + // ES v7 + : \Elastic\Elasticsearch\Client::class; $clientDefinition = new Definition($clientClass); $container->setDefinition('api_platform.elasticsearch.client', $clientDefinition); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php index 1be89c71010..574f35f5d75 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php @@ -40,8 +40,10 @@ public function process(ContainerBuilder $container): void } if (class_exists(\Elasticsearch\ClientBuilder::class)) { + // ES v7 $builderName = \Elasticsearch\ClientBuilder::class; } else { + // ES v8 and up $builderName = \Elastic\Elasticsearch\ClientBuilder::class; } diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index e7895b10ae8..1df39b9de50 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -468,7 +468,12 @@ private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void ->validate() ->ifTrue() ->then(static function (bool $v): bool { - if (!(class_exists(\Elasticsearch\Client::class) || class_exists(\Elastic\Elasticsearch\Client::class))) { + if ( + // ES v7 + !class_exists(\Elasticsearch\Client::class) + // ES v8 and up + && !class_exists(\Elastic\Elasticsearch\Client::class) + ) { throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.'); } diff --git a/tests/Behat/ElasticsearchContext.php b/tests/Behat/ElasticsearchContext.php index 37737cd562f..0395e56f293 100644 --- a/tests/Behat/ElasticsearchContext.php +++ b/tests/Behat/ElasticsearchContext.php @@ -15,6 +15,7 @@ use Behat\Behat\Context\Context; use Elastic\Elasticsearch\Client; +use Elasticsearch\Client as V7Client; use Symfony\Component\Finder\Finder; /** @@ -24,7 +25,7 @@ */ final class ElasticsearchContext implements Context { - public function __construct(private readonly Client $client, private readonly string $elasticsearchMappingsPath, private readonly string $elasticsearchFixturesPath) + public function __construct(private readonly V7Client|Client $client, private readonly string $elasticsearchMappingsPath, private readonly string $elasticsearchFixturesPath) { } diff --git a/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php b/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php index 2245b50df0c..1376fcd0be9 100644 --- a/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php @@ -36,6 +36,7 @@ public function testConstruct(): void public function testProcess(): void { + // ES v7 if (class_exists(\Elasticsearch\ClientBuilder::class)) { $clientBuilder = \Elasticsearch\ClientBuilder::class; @@ -45,6 +46,7 @@ public function testProcess(): void Argument::withEntry('tracer', Argument::type(Reference::class)), Argument::size(3), ]; + // ES v8 and up } else { $clientBuilder = \Elastic\Elasticsearch\ClientBuilder::class; @@ -75,7 +77,11 @@ public function testProcess(): void public function testProcessWithoutConfiguration(): void { - $clientBuilder = class_exists(\Elasticsearch\ClientBuilder::class) ? \Elasticsearch\ClientBuilder::class : \Elastic\Elasticsearch\ClientBuilder::class; + $clientBuilder = class_exists(\Elasticsearch\ClientBuilder::class) + // ES v7 + ? \Elasticsearch\ClientBuilder::class + // ES v8 and up + : \Elastic\Elasticsearch\ClientBuilder::class; $clientDefinitionProphecy = $this->prophesize(Definition::class); $clientDefinitionProphecy->setFactory([$clientBuilder, 'build'])->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled();