diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 347c9fc9911..914070d93e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -520,7 +520,8 @@ jobs: - name: Runs Elasticsearch uses: elastic/elastic-github-actions/elasticsearch@master with: - stack-version: '7.6.0' + stack-version: '8.4.0' + security-enabled: false - name: Setup PHP uses: shivammathur/setup-php@v2 with: @@ -547,6 +548,56 @@ jobs: - name: Run Behat tests run: vendor/bin/behat --out=std --format=progress --profile=elasticsearch --no-interaction + elasticsearch-lowest: + name: Behat (PHP ${{ matrix.php }}) (Elasticsearch) (Symfony lowest) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - '8.2' + fail-fast: false + env: + APP_ENV: elasticsearch + steps: + - name: Checkout + uses: actions/checkout@v3 + - 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.6.0' + - 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@v3 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: composer update --prefer-lowest --no-interaction --no-progress --ansi + - name: Install PHPUnit + run: vendor/bin/simple-phpunit --version + - 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 8d955dbc0b9..93abd25cdec 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "doctrine/mongodb-odm": "^2.2", "doctrine/mongodb-odm-bundle": "^4.0", "doctrine/orm": "^2.14", - "elasticsearch/elasticsearch": "^7.11.0", + "elasticsearch/elasticsearch": "^7.11 || ^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", @@ -97,7 +97,7 @@ "symfony/var-exporter" : "<6.1.1", "phpunit/phpunit": "<9.5", "phpspec/prophecy": "<1.15", - "elasticsearch/elasticsearch": ">=8.0" + "elasticsearch/elasticsearch": ">=8.0,<8.4" }, "suggest": { "doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.", @@ -135,7 +135,8 @@ "sort-packages": true, "allow-plugins": { "composer/package-versions-deprecated": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "php-http/discovery": true } }, "extra": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 513a144518b..3f1afcb6681 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -85,3 +85,5 @@ parameters: - message: '#^Property .+ is unused.$#' path: tests/Doctrine/Odm/PropertyInfo/Fixtures/DoctrineDummy.php + + - '#Elasticsearch\\.*#' diff --git a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php index 12b5dca1d46..fb5fcd92941 100644 --- a/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php +++ b/src/Elasticsearch/Metadata/Document/Factory/CatDocumentMetadataFactory.php @@ -17,6 +17,8 @@ use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Util\Inflector; +use Elastic\Elasticsearch\ClientInterface; +use Elastic\Elasticsearch\Exception\ClientResponseException; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; @@ -30,7 +32,7 @@ */ final class CatDocumentMetadataFactory implements DocumentMetadataFactoryInterface { - public function __construct(private readonly Client $client, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly ?DocumentMetadataFactoryInterface $decorated = null) + public function __construct(private readonly Client|ClientInterface $client, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly ?DocumentMetadataFactoryInterface $decorated = null) { } @@ -63,7 +65,7 @@ public function create(string $resourceClass): DocumentMetadata try { $this->client->cat()->indices(['index' => $index]); - } catch (Missing404Exception) { + } catch (Missing404Exception|ClientResponseException) { return $this->handleNotFound($documentMetadata, $resourceClass); } diff --git a/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php index dbe7e2b634c..f51afd705ff 100644 --- a/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php +++ b/src/Elasticsearch/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactory.php @@ -21,13 +21,16 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\Metadata\Util\Inflector; +use Elastic\Elasticsearch\ClientInterface; +use Elastic\Elasticsearch\Exception\ClientResponseException; +use Elastic\Transport\Exception\NoNodeAvailableException; use Elasticsearch\Client; use Elasticsearch\Common\Exceptions\Missing404Exception; use Elasticsearch\Common\Exceptions\NoNodesAvailableException; final class ElasticsearchProviderResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface { - public function __construct(private readonly Client $client, private readonly ResourceMetadataCollectionFactoryInterface $decorated, private readonly bool $triggerDeprecation = true) + public function __construct(private readonly Client|ClientInterface $client, private readonly ResourceMetadataCollectionFactoryInterface $decorated, private readonly bool $triggerDeprecation = true) { if ($this->triggerDeprecation) { trigger_deprecation('api-platform/core', '3.1', '%s is deprecated and will be removed in v4', self::class); @@ -109,7 +112,7 @@ private function hasIndices(Operation $operation): bool $this->client->cat()->indices(['index' => $index]); return true; - } catch (Missing404Exception|NoNodesAvailableException) { + } catch (Missing404Exception|NoNodesAvailableException|ClientResponseException|NoNodeAvailableException) { return false; } } diff --git a/src/Elasticsearch/Paginator.php b/src/Elasticsearch/Paginator.php index 2e32c67c976..2635be0b62d 100644 --- a/src/Elasticsearch/Paginator.php +++ b/src/Elasticsearch/Paginator.php @@ -95,7 +95,7 @@ public function getIterator(): \Traversable $denormalizationContext = array_merge([AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true], $this->denormalizationContext); foreach ($this->documents['hits']['hits'] ?? [] as $document) { - $cacheKey = isset($document['_index'], $document['_type'], $document['_id']) ? md5("{$document['_index']}_{$document['_type']}_{$document['_id']}") : null; + $cacheKey = isset($document['_index'], $document['_id']) ? md5("{$document['_index']}_{$document['_id']}") : null; if ($cacheKey && \array_key_exists($cacheKey, $this->cachedDenormalizedDocuments)) { $object = $this->cachedDenormalizedDocuments[$cacheKey]; diff --git a/src/Elasticsearch/State/CollectionProvider.php b/src/Elasticsearch/State/CollectionProvider.php index f2b612b8747..80d1b3480ec 100644 --- a/src/Elasticsearch/State/CollectionProvider.php +++ b/src/Elasticsearch/State/CollectionProvider.php @@ -17,11 +17,12 @@ use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Paginator; -use ApiPlatform\Elasticsearch\Util\ElasticsearchVersion; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Util\Inflector; use ApiPlatform\State\Pagination\Pagination; use ApiPlatform\State\ProviderInterface; +use Elastic\Elasticsearch\ClientInterface; +use Elastic\Elasticsearch\Response\Elasticsearch; use Elasticsearch\Client; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -34,9 +35,10 @@ final class CollectionProvider implements ProviderInterface { /** + * @param Client|ClientInterface $client * @param RequestBodySearchCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(private readonly Client $client, private readonly DocumentMetadataFactoryInterface $documentMetadataFactory, private readonly DenormalizerInterface $denormalizer, private readonly Pagination $pagination, private readonly iterable $collectionExtensions = []) + public function __construct(private $client, private readonly DocumentMetadataFactoryInterface $documentMetadataFactory, private readonly DenormalizerInterface $denormalizer, private readonly Pagination $pagination, private readonly iterable $collectionExtensions = []) { } @@ -71,12 +73,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c 'body' => $body, ]; - if (null !== $options->getType() && ElasticsearchVersion::supportsMappingType()) { - $params['type'] = $options->getType(); - } - $documents = $this->client->search($params); + if ($documents instanceof Elasticsearch) { + $documents = $documents->asArray(); + } + return new Paginator( $this->denormalizer, $documents, @@ -89,7 +91,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c private function convertDocumentMetadata(DocumentMetadata $documentMetadata): Options { - return new Options($documentMetadata->getIndex(), $documentMetadata->getType()); + return new Options($documentMetadata->getIndex()); } private function getIndex(Operation $operation): string diff --git a/src/Elasticsearch/State/ItemProvider.php b/src/Elasticsearch/State/ItemProvider.php index bf0723281fd..ce7fb78c832 100644 --- a/src/Elasticsearch/State/ItemProvider.php +++ b/src/Elasticsearch/State/ItemProvider.php @@ -16,11 +16,14 @@ use ApiPlatform\Elasticsearch\Metadata\Document\DocumentMetadata; use ApiPlatform\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface; use ApiPlatform\Elasticsearch\Serializer\DocumentNormalizer; -use ApiPlatform\Elasticsearch\Util\ElasticsearchVersion; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Util\Inflector; use ApiPlatform\State\ProviderInterface; +use Elastic\Elasticsearch\ClientInterface; +use Elastic\Elasticsearch\Exception\ClientResponseException; +use Elastic\Elasticsearch\Response\Elasticsearch; use Elasticsearch\Client; +use Elasticsearch\Common\Exceptions\Missing404Exception; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -32,7 +35,10 @@ */ final class ItemProvider implements ProviderInterface { - public function __construct(private readonly Client $client, private readonly DocumentMetadataFactoryInterface $documentMetadataFactory, private readonly DenormalizerInterface $denormalizer) + /** + * @param Client|ClientInterface $client + */ + public function __construct(private $client, private readonly DocumentMetadataFactoryInterface $documentMetadataFactory, private readonly DenormalizerInterface $denormalizer) { } @@ -51,18 +57,18 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $params = [ - 'client' => ['ignore' => 404], 'index' => $options->getIndex() ?? $this->getIndex($operation), 'id' => (string) reset($uriVariables), ]; - if (null !== $options->getType() && ElasticsearchVersion::supportsMappingType()) { - $params['type'] = $options->getType(); + try { + $document = $this->client->get($params); + } catch (Missing404Exception|ClientResponseException) { + return null; } - $document = $this->client->get($params); - if (!$document['found']) { - return null; + if ($document instanceof Elasticsearch) { + $document = $document->asArray(); } $item = $this->denormalizer->denormalize($document, $resourceClass, DocumentNormalizer::FORMAT, [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true]); @@ -75,7 +81,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c private function convertDocumentMetadata(DocumentMetadata $documentMetadata): Options { - return new Options($documentMetadata->getIndex(), $documentMetadata->getType()); + return new Options($documentMetadata->getIndex()); } private function getIndex(Operation $operation): string diff --git a/src/Elasticsearch/State/Options.php b/src/Elasticsearch/State/Options.php index 85fbd8b3ccf..fc82af387bc 100644 --- a/src/Elasticsearch/State/Options.php +++ b/src/Elasticsearch/State/Options.php @@ -19,7 +19,6 @@ class Options implements OptionsInterface { public function __construct( protected ?string $index = null, - protected ?string $type = null, ) { } @@ -35,17 +34,4 @@ public function withIndex(?string $index): self return $self; } - - public function getType(): ?string - { - return $this->type; - } - - public function withType(?string $type): self - { - $self = clone $this; - $self->type = $type; - - return $self; - } } diff --git a/src/Elasticsearch/Tests/Metadata/Document/Factory/CatDocumentMetadataFactoryTest.php b/src/Elasticsearch/Tests/Metadata/Document/Factory/CatDocumentMetadataFactoryTest.php index be3cba6d7a6..6a2178cbf19 100644 --- a/src/Elasticsearch/Tests/Metadata/Document/Factory/CatDocumentMetadataFactoryTest.php +++ b/src/Elasticsearch/Tests/Metadata/Document/Factory/CatDocumentMetadataFactoryTest.php @@ -33,6 +33,13 @@ class CatDocumentMetadataFactoryTest extends TestCase { use ProphecyTrait; + protected function setUp(): void + { + if (class_exists(\Elastic\Elasticsearch\ClientInterface::class)) { + $this->markTestSkipped('\Elastic\Elasticsearch\ClientInterface doesn\'t have cat method signature.'); + } + } + public function testConstruct(): void { self::assertInstanceOf( diff --git a/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php b/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php index d9899f1f82b..2e1cd56e246 100644 --- a/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php +++ b/src/Elasticsearch/Tests/Metadata/Resource/Factory/ElasticsearchProviderResourceMetadataCollectionFactoryTest.php @@ -37,11 +37,13 @@ class ElasticsearchProviderResourceMetadataCollectionFactoryTest extends TestCas public function testConstruct(): void { + $client = class_exists(Client::class) ? Client::class : \Elastic\Elasticsearch\ClientInterface::class; + $this->expectDeprecation('Since api-platform/core 3.1: ApiPlatform\Elasticsearch\Metadata\Resource\Factory\ElasticsearchProviderResourceMetadataCollectionFactory is deprecated and will be removed in v4'); self::assertInstanceOf( ResourceMetadataCollectionFactoryInterface::class, new ElasticsearchProviderResourceMetadataCollectionFactory( - $this->prophesize(Client::class)->reveal(), + $this->prophesize($client)->reveal(), $this->prophesize(ResourceMetadataCollectionFactoryInterface::class)->reveal() ) ); @@ -52,6 +54,10 @@ public function testConstruct(): void */ public function testCreate(?bool $elasticsearchFlag, int $expectedCatCallCount, ?bool $expectedResult): void { + if (class_exists(\Elastic\Elasticsearch\ClientInterface::class)) { + $this->markTestSkipped('\Elastic\Elasticsearch\ClientInterface doesn\'t have cat method signature.'); + } + if (null !== $elasticsearchFlag) { $solution = $elasticsearchFlag ? sprintf('Pass an instance of %s to $stateOptions instead', Options::class) diff --git a/src/Elasticsearch/Tests/State/CollectionProviderTest.php b/src/Elasticsearch/Tests/State/CollectionProviderTest.php index a3f443910c6..4aad8a2c9b6 100644 --- a/src/Elasticsearch/Tests/State/CollectionProviderTest.php +++ b/src/Elasticsearch/Tests/State/CollectionProviderTest.php @@ -22,7 +22,6 @@ use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; use ApiPlatform\State\Pagination\Pagination; -use Elasticsearch\Client; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -38,10 +37,12 @@ final class CollectionProviderTest extends TestCase public function testConstruct(): void { + $client = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\ClientInterface::class; + self::assertInstanceOf( CollectionProvider::class, new CollectionProvider( - $this->prophesize(Client::class)->reveal(), + $this->prophesize($client)->reveal(), $this->prophesize(DocumentMetadataFactoryInterface::class)->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal(), new Pagination() @@ -99,7 +100,9 @@ public function testGetCollection(): void ], ]; - $clientProphecy = $this->prophesize(Client::class); + $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\ClientInterface::class; + + $clientProphecy = $this->prophesize($clientClass); $clientProphecy ->search( Argument::allOf( diff --git a/src/Elasticsearch/Tests/State/ItemProviderTest.php b/src/Elasticsearch/Tests/State/ItemProviderTest.php index 8f19d886e10..de56fcbc696 100644 --- a/src/Elasticsearch/Tests/State/ItemProviderTest.php +++ b/src/Elasticsearch/Tests/State/ItemProviderTest.php @@ -18,7 +18,6 @@ use ApiPlatform\Elasticsearch\State\ItemProvider; use ApiPlatform\Elasticsearch\Tests\Fixtures\Foo; use ApiPlatform\Metadata\Get; -use Elasticsearch\Client; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; @@ -34,10 +33,12 @@ final class ItemProviderTest extends TestCase public function testConstruct(): void { + $client = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\ClientInterface::class; + self::assertInstanceOf( ItemProvider::class, new ItemProvider( - $this->prophesize(Client::class)->reveal(), + $this->prophesize($client)->reveal(), $this->prophesize(DocumentMetadataFactoryInterface::class)->reveal(), $this->prophesize(DenormalizerInterface::class)->reveal() ) @@ -64,8 +65,10 @@ public function testGetItem(): void $foo->setName('Rossinière'); $foo->setBar('erèinissor'); - $clientProphecy = $this->prophesize(Client::class); - $clientProphecy->get(['client' => ['ignore' => 404], 'index' => 'foo', 'id' => '1'])->willReturn($document)->shouldBeCalled(); + $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\ClientInterface::class; + + $clientProphecy = $this->prophesize($clientClass); + $clientProphecy->get(['index' => 'foo', 'id' => '1'])->willReturn($document)->shouldBeCalled(); $denormalizerProphecy = $this->prophesize(DenormalizerInterface::class); $denormalizerProphecy->denormalize($document, Foo::class, DocumentNormalizer::FORMAT, [AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => true])->willReturn($foo)->shouldBeCalled(); @@ -79,8 +82,10 @@ public function testGetInexistantItem(): void { $documentMetadataFactoryProphecy = $this->prophesize(DocumentMetadataFactoryInterface::class); - $clientProphecy = $this->prophesize(Client::class); - $clientProphecy->get(['client' => ['ignore' => 404], 'index' => 'foo', 'id' => '404'])->willReturn([ + $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\ClientInterface::class; + + $clientProphecy = $this->prophesize($clientClass); + $clientProphecy->get(['index' => 'foo', 'id' => '404'])->willReturn([ '_index' => 'foo', '_type' => '_doc', '_id' => '404', diff --git a/src/Elasticsearch/Tests/Util/ElasticsearchVersionTest.php b/src/Elasticsearch/Tests/Util/ElasticsearchVersionTest.php deleted file mode 100644 index ea7a8c25f85..00000000000 --- a/src/Elasticsearch/Tests/Util/ElasticsearchVersionTest.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Elasticsearch\Tests\Util; - -use ApiPlatform\Elasticsearch\Util\ElasticsearchVersion; -use PHPUnit\Framework\TestCase; - -class ElasticsearchVersionTest extends TestCase -{ - /** - * @dataProvider supportsDocumentTypeProvider - */ - public function testSupportsDocumentType(string $version, bool $expected): void - { - self::assertSame($expected, ElasticsearchVersion::supportsMappingType($version)); - } - - public static function supportsDocumentTypeProvider(): \Generator - { - yield 'ES 5' => ['5.5.0', true]; - yield 'ES 5 dev' => ['5.x', true]; - yield 'ES 6' => ['6.8.2', true]; - yield 'ES 7' => ['7.17.0', false]; - yield 'ES 8' => ['8.1.0', false]; - yield 'ES 8 dev' => ['8.x', false]; - } -} diff --git a/src/Elasticsearch/Util/ElasticsearchVersion.php b/src/Elasticsearch/Util/ElasticsearchVersion.php deleted file mode 100644 index b9e4b914da1..00000000000 --- a/src/Elasticsearch/Util/ElasticsearchVersion.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Elasticsearch\Util; - -use Elasticsearch\Client; - -class ElasticsearchVersion -{ - final public const REGEX_PATTERN = '/\d(.*)/'; - - /** - * Detect whether the current ES version supports passing mapping type as a search parameter. - * - * @see https://www.elastic.co/guide/en/elasticsearch/reference/7.17/removal-of-types.html#_schedule_for_removal_of_mapping_types - */ - public static function supportsMappingType(string $version = Client::VERSION): bool - { - $matchResult = preg_match(self::REGEX_PATTERN, $version, $matches); - - return \is_int($matchResult) && $matchResult > 0 && (int) $matches[0] < 7; - } -} diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index b2395b2fa13..e77f5be5d21 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -754,6 +754,13 @@ private function registerElasticsearchConfiguration(ContainerBuilder $container, return; } + $clientClass = class_exists(\Elasticsearch\Client::class) ? \Elasticsearch\Client::class : \Elastic\Elasticsearch\Client::class; + + $clientDefinition = new Definition($clientClass); + $clientDefinition->setPublic(false); + + $container->setDefinition('api_platform.elasticsearch.client', $clientDefinition); + $loader->load('elasticsearch.xml'); $container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class) diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php index 0d6f5f0ea90..bc1799a8943 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler; -use Elasticsearch\ClientBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -40,19 +39,28 @@ public function process(ContainerBuilder $container): void $clientConfiguration['hosts'] = $hosts; } + if (class_exists(\Elasticsearch\ClientBuilder::class)) { + $builderName = \Elasticsearch\ClientBuilder::class; + } else { + $builderName = \Elastic\Elasticsearch\ClientBuilder::class; + } + if ($container->has('logger')) { $clientConfiguration['logger'] = new Reference('logger'); - $clientConfiguration['tracer'] = new Reference('logger'); + + if (\Elasticsearch\ClientBuilder::class === $builderName) { + $clientConfiguration['tracer'] = new Reference('logger'); + } } $clientDefinition = $container->getDefinition('api_platform.elasticsearch.client'); if (!$clientConfiguration) { // @noRector \Rector\Php81\Rector\Array_\FirstClassCallableRector - $clientDefinition->setFactory([ClientBuilder::class, 'build']); + $clientDefinition->setFactory([$builderName, 'build']); } else { // @noRector \Rector\Php81\Rector\Array_\FirstClassCallableRector - $clientDefinition->setFactory([ClientBuilder::class, 'fromConfig']); + $clientDefinition->setFactory([$builderName, 'fromConfig']); $clientDefinition->setArguments([$clientConfiguration]); } } diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 063d66a58d7..c5e875d11e6 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -25,7 +25,6 @@ use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\OptimisticLockException; -use Elasticsearch\Client as ElasticsearchClient; use GraphQL\GraphQL; use Symfony\Bundle\FullStack; use Symfony\Bundle\MakerBundle\MakerBundle; @@ -419,7 +418,7 @@ private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void ->validate() ->ifTrue() ->then(static function (bool $v): bool { - if (!class_exists(ElasticsearchClient::class)) { + if (!(class_exists(\Elasticsearch\Client::class) || class_exists(\Elastic\Elasticsearch\Client::class))) { throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.'); } diff --git a/src/Symfony/Bundle/Resources/config/elasticsearch.xml b/src/Symfony/Bundle/Resources/config/elasticsearch.xml index 42406b7c91f..15618ed939b 100644 --- a/src/Symfony/Bundle/Resources/config/elasticsearch.xml +++ b/src/Symfony/Bundle/Resources/config/elasticsearch.xml @@ -5,8 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - diff --git a/tests/Behat/ElasticsearchContext.php b/tests/Behat/ElasticsearchContext.php index b7f5483776b..cd5e3cf4949 100644 --- a/tests/Behat/ElasticsearchContext.php +++ b/tests/Behat/ElasticsearchContext.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Tests\Behat; use Behat\Behat\Context\Context; +use Elastic\Elasticsearch\ClientInterface; use Elasticsearch\Client; 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 Client|ClientInterface $client, private readonly string $elasticsearchMappingsPath, private readonly string $elasticsearchFixturesPath) { } @@ -88,15 +89,17 @@ private function deleteIndexes(): void $finder = new Finder(); $finder->files()->in($this->elasticsearchMappingsPath)->name('*.json'); - $indexClient = $this->client->indices(); + $indexes = []; foreach ($finder as $file) { - $index = $file->getBasename('.json'); - if (!$indexClient->exists(['index' => $index])) { - continue; - } + $indexes[] = $file->getBasename('.json'); + } - $indexClient->delete(['index' => $index]); + if ([] !== $indexes) { + $this->client->indices()->delete([ + 'index' => implode(',', $indexes), + 'ignore_unavailable' => true, + ]); } } @@ -113,9 +116,9 @@ private function loadFixtures(): void foreach (json_decode($file->getContents(), true, 512, \JSON_THROW_ON_ERROR) as $document) { if (null === ($document['id'] ?? null)) { - $bulk[] = ['index' => ['_index' => $index, '_type' => '_doc']]; + $bulk[] = ['index' => ['_index' => $index]]; } else { - $bulk[] = ['create' => ['_index' => $index, '_type' => '_doc', '_id' => (string) $document['id']]]; + $bulk[] = ['create' => ['_index' => $index, '_id' => (string) $document['id']]]; } $bulk[] = $document; diff --git a/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php b/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php index 56f4577f028..3cb5f70410a 100644 --- a/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php +++ b/tests/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPassTest.php @@ -14,7 +14,6 @@ namespace ApiPlatform\Tests\Symfony\Bundle\DependencyInjection\Compiler; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass; -use Elasticsearch\ClientBuilder; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -37,17 +36,31 @@ public function testConstruct(): void public function testProcess(): void { + if (class_exists(\Elasticsearch\ClientBuilder::class)) { + $clientBuilder = \Elasticsearch\ClientBuilder::class; + + $expectedArguments = [ + Argument::withEntry('hosts', ['http://localhost:9200']), + Argument::withEntry('logger', Argument::type(Reference::class)), + Argument::withEntry('tracer', Argument::type(Reference::class)), + Argument::size(3), + ]; + } else { + $clientBuilder = \Elastic\Elasticsearch\ClientBuilder::class; + + $expectedArguments = [ + Argument::withEntry('hosts', ['http://localhost:9200']), + Argument::withEntry('logger', Argument::type(Reference::class)), + Argument::size(2), + ]; + } + $clientDefinitionProphecy = $this->prophesize(Definition::class); // @noRector \Rector\Php81\Rector\Array_\FirstClassCallableRector - $clientDefinitionProphecy->setFactory([ClientBuilder::class, 'fromConfig'])->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled(); + $clientDefinitionProphecy->setFactory([$clientBuilder, 'fromConfig'])->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled(); $clientDefinitionProphecy->setArguments( Argument::allOf( - Argument::withEntry(0, Argument::allOf( - Argument::withEntry('hosts', ['http://localhost:9200']), - Argument::withEntry('logger', Argument::type(Reference::class)), - Argument::withEntry('tracer', Argument::type(Reference::class)), - Argument::size(3) - )), + Argument::withEntry(0, Argument::allOf(...$expectedArguments)), Argument::size(1) ) )->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled(); @@ -63,9 +76,11 @@ public function testProcess(): void public function testProcessWithoutConfiguration(): void { + $clientBuilder = class_exists(\Elasticsearch\ClientBuilder::class) ? \Elasticsearch\ClientBuilder::class : \Elastic\Elasticsearch\ClientBuilder::class; + $clientDefinitionProphecy = $this->prophesize(Definition::class); // @noRector \Rector\Php81\Rector\Array_\FirstClassCallableRector - $clientDefinitionProphecy->setFactory([ClientBuilder::class, 'build'])->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled(); + $clientDefinitionProphecy->setFactory([$clientBuilder, 'build'])->willReturn($clientDefinitionProphecy->reveal())->shouldBeCalled(); $containerBuilderProphecy = $this->prophesize(ContainerBuilder::class); $containerBuilderProphecy->getParameter('api_platform.elasticsearch.enabled')->willReturn(true)->shouldBeCalled();