From fa3b8c4d81c6403719d542af6b1c655039e14f40 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 10:31:51 +0300 Subject: [PATCH 01/16] chore: upgrade to v4 --- composer.json | 2 +- src/AlgoliaSearchBundle.php | 6 +- src/Command/SearchClearCommand.php | 4 +- src/Command/SearchImportCommand.php | 17 ++++- src/Engine.php | 98 +++++++++++++------------ src/Resources/config/services.yaml | 6 +- src/Responses/NullResponse.php | 14 ++++ src/Responses/SearchServiceResponse.php | 28 ++++--- src/SearchService.php | 23 +++--- src/Services/AlgoliaSearchService.php | 27 ++++--- src/Services/NullSearchService.php | 25 +++---- src/Settings/SettingsManager.php | 8 +- tests/TestCase/ClientProxyTest.php | 2 +- tests/TestCase/CommandsTest.php | 30 +++++--- tests/TestCase/DoctrineTest.php | 11 +-- tests/TestCase/EngineTest.php | 18 +++-- tests/TestCase/SearchServiceTest.php | 6 +- tests/TestCase/SerializationTest.php | 1 - tests/TestCase/SettingsTest.php | 35 +++++---- 19 files changed, 204 insertions(+), 157 deletions(-) create mode 100644 src/Responses/NullResponse.php diff --git a/composer.json b/composer.json index 4281f467..2adf62f3 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "prefer-stable": true, "require": { "php": ">= 8.2", - "algolia/algoliasearch-client-php": "^3.0", + "algolia/algoliasearch-client-php": "^4.0", "doctrine/event-manager": "^1.1 || ^2.0", "doctrine/persistence": "^2.1 || ^3.0 || ^4.0", "symfony/filesystem": "^7.0 || ^8.0", diff --git a/src/AlgoliaSearchBundle.php b/src/AlgoliaSearchBundle.php index 56a9485a..e3892e3a 100644 --- a/src/AlgoliaSearchBundle.php +++ b/src/AlgoliaSearchBundle.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle; -use Algolia\AlgoliaSearch\Support\UserAgent; +use Algolia\AlgoliaSearch\Support\AlgoliaAgent; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Kernel as SfKernel; @@ -17,7 +17,7 @@ public function boot(): void { parent::boot(); - UserAgent::addCustomUserAgent('Symfony Search Bundle', self::VERSION); - UserAgent::addCustomUserAgent('Symfony', SfKernel::VERSION); + AlgoliaAgent::addAlgoliaAgent('SearchClient', 'Symfony Search Bundle', self::VERSION); + AlgoliaAgent::addAlgoliaAgent('SearchClient', 'Symfony', SfKernel::VERSION); } } diff --git a/src/Command/SearchClearCommand.php b/src/Command/SearchClearCommand.php index cd006ce8..ab8b859b 100644 --- a/src/Command/SearchClearCommand.php +++ b/src/Command/SearchClearCommand.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle\Command; -use Algolia\AlgoliaSearch\Response\IndexingResponse; +use Algolia\SearchBundle\Responses\NullResponse; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -34,7 +34,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($indexToClear as $indexName => $className) { $success = $this->searchService->clear($className); - if ($success instanceof IndexingResponse) { + if (!$success instanceof NullResponse) { $output->writeln('Cleared ' . $indexName . ' index of ' . $className . ' '); } else { $output->writeln('Index ' . $indexName . ' couldn\'t be cleared'); diff --git a/src/Command/SearchImportCommand.php b/src/Command/SearchImportCommand.php index 81391132..8e529821 100644 --- a/src/Command/SearchImportCommand.php +++ b/src/Command/SearchImportCommand.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle\Command; -use Algolia\AlgoliaSearch\SearchClient; +use Algolia\AlgoliaSearch\Api\SearchClient; use Algolia\SearchBundle\Entity\Aggregator; use Algolia\SearchBundle\SearchService; use Doctrine\Persistence\ManagerRegistry; @@ -81,7 +81,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($shouldDoAtomicReindex) { $temporaryIndexName = $this->searchServiceForAtomicReindex->searchableAs($entityClassName); $output->writeln("Creating temporary index $temporaryIndexName"); - $this->searchClient->copyIndex($sourceIndexName, $temporaryIndexName, ['scope' => ['settings', 'synonyms', 'rules']]); + $this->searchClient->operationIndex($sourceIndexName, [ + 'operation' => 'copy', + 'destination' => $temporaryIndexName, + 'scope' => ['settings', 'synonyms', 'rules'], + ]); } $allResponses = []; @@ -125,7 +129,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $response->wait(); } $output->writeln("Moving $indexName -> $sourceIndexName\n"); - $this->searchClient->moveIndex($indexName, $sourceIndexName); + $this->searchClient->operationIndex($indexName, [ + 'operation' => 'move', + 'destination' => $sourceIndexName, + ]); } } @@ -149,7 +156,9 @@ private function formatIndexingResponse($batch) $formattedResponse[$indexName] = 0; } - $formattedResponse[$indexName] += count($apiResponse->current()['objectIDs']); + foreach ($apiResponse as $batchResponse) { + $formattedResponse[$indexName] += count($batchResponse['objectIDs']); + } } } diff --git a/src/Engine.php b/src/Engine.php index 643c9b69..698a6d55 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -2,10 +2,8 @@ namespace Algolia\SearchBundle; -use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; -use Algolia\AlgoliaSearch\Response\BatchIndexingResponse; -use Algolia\AlgoliaSearch\Response\NullResponse; -use Algolia\AlgoliaSearch\SearchClient; +use Algolia\AlgoliaSearch\Api\SearchClient; +use Algolia\SearchBundle\Responses\NullResponse; /** * @internal @@ -20,16 +18,24 @@ public function __construct(SearchClient $client) $this->client = $client; } + /** + * @return SearchClient + */ + public function getClient() + { + return $this->client; + } + /** * Add new objects to an index. * * This method allows you to create records on your index by sending one or more objects. * Each object contains a set of attributes and values, which represents a full record on an index. * - * @param array $searchableEntities - * @param array|RequestOptions $requestOptions + * @param array $searchableEntities + * @param array $requestOptions * - * @return array + * @return array * * @throws \Algolia\AlgoliaSearch\Exceptions\AlgoliaException */ @@ -59,13 +65,8 @@ public function index($searchableEntities, $requestOptions) } $result = []; - if (!array_key_exists('autoGenerateObjectIDIfNotExist', $requestOptions)) { - $requestOptions['autoGenerateObjectIDIfNotExist'] = true; - } foreach ($data as $indexName => $objects) { - $result[$indexName] = $this->client - ->initIndex($indexName) - ->saveObjects($objects, $requestOptions); + $result[$indexName] = $this->client->saveObjects($indexName, $objects); } return $result; @@ -76,10 +77,10 @@ public function index($searchableEntities, $requestOptions) * * This method enables you to remove one or more objects from an index. * - * @param array $searchableEntities - * @param array|RequestOptions $requestOptions + * @param array $searchableEntities + * @param array $requestOptions * - * @return array + * @return array * * @throws \Algolia\AlgoliaSearch\Exceptions\AlgoliaException */ @@ -106,9 +107,7 @@ public function remove($searchableEntities, $requestOptions) $result = []; foreach ($data as $indexName => $objects) { - $result[$indexName] = $this->client - ->initIndex($indexName) - ->deleteObjects($objects, $requestOptions); + $result[$indexName] = $this->client->deleteObjects($indexName, $objects); } return $result; @@ -117,23 +116,24 @@ public function remove($searchableEntities, $requestOptions) /** * Clear the records of an index without affecting its settings. * - * This method enables you to delete an index’s contents (records) without + * This method enables you to delete an index's contents (records) without * removing any settings, rules and synonyms. * * If you want to remove the entire index and not just its records, use the * delete method instead. * - * @param string $indexName - * @param array|RequestOptions $requestOptions + * @param string $indexName + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function clear($indexName, $requestOptions) { - $index = $this->client->initIndex($indexName); + if ($this->client->indexExists($indexName)) { + $response = $this->client->clearObjects($indexName); + $this->client->waitForTask($indexName, $response['taskID']); - if ($index->exists($requestOptions)) { - return $index->clearObjects($requestOptions); + return $response; } return new NullResponse(); @@ -146,19 +146,20 @@ public function clear($indexName, $requestOptions) * removes its metadata and configured settings (like searchable attributes or custom ranking). * * If the index has replicas, they will be preserved but will no longer be - * linked to their primary index. Instead, they’ll become independent indices. + * linked to their primary index. Instead, they'll become independent indices. * - * @param string $indexName - * @param array|RequestOptions $requestOptions + * @param string $indexName + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function delete($indexName, $requestOptions) { - $index = $this->client->initIndex($indexName); + if ($this->client->indexExists($indexName)) { + $response = $this->client->deleteIndex($indexName); + $this->client->waitForTask($indexName, $response['taskID']); - if ($index->exists($requestOptions)) { - return $index->delete($requestOptions); + return $response; } return new NullResponse(); @@ -167,9 +168,9 @@ public function delete($indexName, $requestOptions) /** * Method used for querying an index. * - * @param string $query - * @param string $indexName - * @param array|RequestOptions $requestOptions + * @param string $query + * @param string $indexName + * @param array $requestOptions * * @return array * @@ -177,15 +178,20 @@ public function delete($indexName, $requestOptions) */ public function search($query, $indexName, $requestOptions) { - return $this->client->initIndex($indexName)->search($query, $requestOptions); + $searchParams = array_merge( + ['query' => $query], + is_array($requestOptions) ? $requestOptions : [] + ); + + return $this->client->searchSingleIndex($indexName, $searchParams); } /** * Search the index and returns the objectIDs. * - * @param string $query - * @param string $indexName - * @param array|RequestOptions $requestOptions + * @param string $query + * @param string $indexName + * @param array $requestOptions * * @return array * @@ -195,15 +201,17 @@ public function searchIds($query, $indexName, $requestOptions) { $result = $this->search($query, $indexName, $requestOptions); - return array_column($result['hits'], 'objectID'); + return array_map(function ($hit) { + return $hit['objectID']; + }, $result['hits']); } /** * Search the index and returns the number of results. * - * @param string $query - * @param string $indexName - * @param array|RequestOptions $requestOptions + * @param string $query + * @param string $indexName + * @param array $requestOptions * * @return int * @@ -211,7 +219,7 @@ public function searchIds($query, $indexName, $requestOptions) */ public function count($query, $indexName, $requestOptions) { - $results = $this->client->initIndex($indexName)->search($query, $requestOptions); + $results = $this->search($query, $indexName, $requestOptions); return (int) $results['nbHits']; } diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 48dc7f6c..9ad4986f 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -23,15 +23,15 @@ services: - { name: doctrine_mongodb.odm.event_subscriber } search.client: - class: Algolia\AlgoliaSearch\SearchClient + class: Algolia\AlgoliaSearch\Api\SearchClient public: true lazy: true - factory: ['Algolia\AlgoliaSearch\SearchClient', 'create'] + factory: ['Algolia\AlgoliaSearch\Api\SearchClient', 'create'] arguments: $appId: '%env(ALGOLIA_APP_ID)%' $apiKey: '%env(ALGOLIA_API_KEY)%' - Algolia\AlgoliaSearch\SearchClient: + Algolia\AlgoliaSearch\Api\SearchClient: alias: search.client Algolia\SearchBundle\SearchService: diff --git a/src/Responses/NullResponse.php b/src/Responses/NullResponse.php new file mode 100644 index 00000000..e79c6012 --- /dev/null +++ b/src/Responses/NullResponse.php @@ -0,0 +1,14 @@ +> $apiResponse + * @param array> $apiResponse */ - public function __construct($apiResponse) + public function __construct(SearchClient $client, $apiResponse) { + $this->client = $client; $this->apiResponse = $apiResponse; } /** - * @param array|mixed $requestOptions - * * @return void */ - public function wait($requestOptions = []) + public function wait() { foreach ($this->apiResponse as $chunk) { - foreach ($chunk as $indexName => $apiResponse) { - /* @var $apiResponse AbstractResponse */ - $apiResponse->wait($requestOptions); + foreach ($chunk as $indexName => $batchResponses) { + foreach ($batchResponses as $batchResponse) { + $this->client->waitForTask($indexName, $batchResponse['taskID']); + } } } } @@ -42,7 +48,7 @@ public function rewind(): void } /** - * @return array + * @return array */ public function current(): array { diff --git a/src/SearchService.php b/src/SearchService.php index 3b3a97e0..85e07bf8 100644 --- a/src/SearchService.php +++ b/src/SearchService.php @@ -2,7 +2,6 @@ namespace Algolia\SearchBundle; -use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Doctrine\Persistence\ObjectManager; interface SearchService @@ -35,40 +34,40 @@ public function searchableAs($className); /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []); /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []); /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function clear($className, $requestOptions = []); /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function delete($className, $requestOptions = []); /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -79,7 +78,7 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -90,7 +89,7 @@ public function rawSearch($className, $query = '', $requestOptions = []); /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return int * diff --git a/src/Services/AlgoliaSearchService.php b/src/Services/AlgoliaSearchService.php index 24405b9f..888f6320 100644 --- a/src/Services/AlgoliaSearchService.php +++ b/src/Services/AlgoliaSearchService.php @@ -2,7 +2,6 @@ namespace Algolia\SearchBundle\Services; -use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Algolia\SearchBundle\Engine; use Algolia\SearchBundle\Entity\Aggregator; use Algolia\SearchBundle\Responses\SearchServiceResponse; @@ -121,9 +120,9 @@ public function searchableAs($className) /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -153,9 +152,9 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -173,9 +172,9 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function clear($className, $requestOptions = []) { @@ -186,9 +185,9 @@ public function clear($className, $requestOptions = []) /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function delete($className, $requestOptions = []) { @@ -200,7 +199,7 @@ public function delete($className, $requestOptions = []) /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -237,7 +236,7 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -253,7 +252,7 @@ public function rawSearch($className, $query = '', $requestOptions = []) /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return int * @@ -392,7 +391,7 @@ private function setIndexIfMapping() * @param array $entities * @param callable $operation * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ private function makeSearchServiceResponseFrom(ObjectManager $objectManager, array $entities, $operation) { @@ -414,7 +413,7 @@ private function makeSearchServiceResponseFrom(ObjectManager $objectManager, arr $batch[] = $operation($searchableEntitiesChunk); } - return new SearchServiceResponse($batch); + return new SearchServiceResponse($this->engine->getClient(), $batch); } /** diff --git a/src/Services/NullSearchService.php b/src/Services/NullSearchService.php index ecdc1172..9cf96708 100644 --- a/src/Services/NullSearchService.php +++ b/src/Services/NullSearchService.php @@ -2,8 +2,7 @@ namespace Algolia\SearchBundle\Services; -use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; -use Algolia\AlgoliaSearch\Response\NullResponse; +use Algolia\SearchBundle\Responses\NullResponse; use Algolia\SearchBundle\SearchService; use Doctrine\Persistence\ObjectManager; @@ -53,9 +52,9 @@ public function searchableAs($className) /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -64,9 +63,9 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption /** * @param object|array $searchables - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -75,9 +74,9 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function clear($className, $requestOptions = []) { @@ -86,9 +85,9 @@ public function clear($className, $requestOptions = []) /** * @param string $className - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * - * @return \Algolia\AlgoliaSearch\Response\AbstractResponse + * @return mixed */ public function delete($className, $requestOptions = []) { @@ -98,7 +97,7 @@ public function delete($className, $requestOptions = []) /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -114,7 +113,7 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return array * @@ -138,7 +137,7 @@ public function rawSearch($className, $query = '', $requestOptions = []) /** * @param string $className * @param string $query - * @param array|RequestOptions $requestOptions + * @param array $requestOptions * * @return int * diff --git a/src/Settings/SettingsManager.php b/src/Settings/SettingsManager.php index af241d8c..fd23b643 100644 --- a/src/Settings/SettingsManager.php +++ b/src/Settings/SettingsManager.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle\Settings; -use Algolia\AlgoliaSearch\SearchClient; +use Algolia\AlgoliaSearch\Api\SearchClient; use Symfony\Component\Filesystem\Filesystem; /** @@ -45,8 +45,7 @@ public function backup(array $params) } foreach ($indices as $indexName) { - $index = $this->algolia->initIndex($indexName); - $settings = $index->getSettings(); + $settings = $this->algolia->getSettings($indexName); $filename = $this->getFileName($indexName, 'settings'); $fs->dumpFile($filename, json_encode($settings, JSON_PRETTY_PRINT)); @@ -71,9 +70,8 @@ public function push(array $params) $filename = $this->getFileName($indexName, 'settings'); if (is_readable($filename)) { - $index = $this->algolia->initIndex($indexName); $settings = json_decode(file_get_contents($filename), true); - $index->setSettings($settings); + $this->algolia->setSettings($indexName, $settings); $output[] = "Pushed settings for $indexName"; } diff --git a/tests/TestCase/ClientProxyTest.php b/tests/TestCase/ClientProxyTest.php index 62c3fc52..12225333 100644 --- a/tests/TestCase/ClientProxyTest.php +++ b/tests/TestCase/ClientProxyTest.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle\TestCase; -use Algolia\AlgoliaSearch\SearchClient; +use Algolia\AlgoliaSearch\Api\SearchClient; use Algolia\SearchBundle\BaseTest; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; diff --git a/tests/TestCase/CommandsTest.php b/tests/TestCase/CommandsTest.php index f26ef27f..b831f66b 100644 --- a/tests/TestCase/CommandsTest.php +++ b/tests/TestCase/CommandsTest.php @@ -19,7 +19,6 @@ class CommandsTest extends BaseTest protected $application; protected $indexName; protected $platform; - protected $index; public function setUp(): void { @@ -30,12 +29,15 @@ public function setUp(): void $this->connection = $this->get('doctrine')->getConnection(); $this->platform = $this->connection->getDatabasePlatform(); $this->indexName = 'posts'; - $this->index = $this->client->initIndex($this->getPrefix() . $this->indexName); - $this->index->setSettings($this->getDefaultConfig())->wait(); + + $fullIndexName = $this->getPrefix() . $this->indexName; + $response = $this->client->setSettings($fullIndexName, $this->getDefaultConfig()); + $this->client->waitForTask($fullIndexName, $response['taskID']); $contentsIndexName = 'contents'; - $contentsIndex = $this->client->initIndex($this->getPrefix() . $contentsIndexName); - $contentsIndex->setSettings($this->getDefaultConfig())->wait(); + $fullContentsName = $this->getPrefix() . $contentsIndexName; + $response = $this->client->setSettings($fullContentsName, $this->getDefaultConfig()); + $this->client->waitForTask($fullContentsName, $response['taskID']); $this->application = new Application(self::$kernel); $this->refreshDb($this->application); @@ -43,9 +45,9 @@ public function setUp(): void public function cleanUp(): void { - $this->searchService->delete(Post::class)->wait(); - $this->searchService->delete(Comment::class)->wait(); - $this->searchService->delete(ContentAggregator::class)->wait(); + $this->searchService->delete(Post::class); + $this->searchService->delete(Comment::class); + $this->searchService->delete(ContentAggregator::class); } public function testSearchClearUnknownIndex(): void @@ -179,7 +181,9 @@ public function testSearchSettingsBackupCommand(): void 'hitsPerPage' => 51, 'maxValuesPerFacet' => 99, ]; - $this->index->setSettings($settingsToUpdate)->wait(); + $fullIndexName = $this->getPrefix() . $this->indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); $command = $this->application->find('search:settings:backup'); $commandTester = new CommandTester($command); $commandTester->execute([ @@ -204,8 +208,10 @@ public function testSearchSettingsPushCommand(): void 'hitsPerPage' => 50, 'maxValuesPerFacet' => 100, ]; - $this->index->setSettings($settingsToUpdate)->wait(); - $settings = $this->index->getSettings(); + $fullIndexName = $this->getPrefix() . $this->indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); + $settings = $this->client->getSettings($fullIndexName); $settingsFile = $this->getFileName($this->indexName, 'settings'); $settingsFileContent = json_decode(file_get_contents($settingsFile), true); @@ -226,7 +232,7 @@ public function testSearchSettingsPushCommand(): void // check if the settings were imported $iteration = 0; do { - $newSettings = $this->index->getSettings(); + $newSettings = $this->client->getSettings($fullIndexName); sleep(1); $iteration++; } while ($newSettings['hitsPerPage'] !== $settingsFileContent['hitsPerPage'] || $iteration < 10); diff --git a/tests/TestCase/DoctrineTest.php b/tests/TestCase/DoctrineTest.php index 60baa45f..b7c324e8 100644 --- a/tests/TestCase/DoctrineTest.php +++ b/tests/TestCase/DoctrineTest.php @@ -24,15 +24,16 @@ public function setUp(): void $client = $this->get('search.client'); $indexName = 'posts'; - $index = $client->initIndex($this->getPrefix() . $indexName); - $index->setSettings($this->getDefaultConfig())->wait(); + $fullIndexName = $this->getPrefix() . $indexName; + $response = $client->setSettings($fullIndexName, $this->getDefaultConfig()); + $client->waitForTask($fullIndexName, $response['taskID']); } public function cleanUp(): void { - $this->searchService->delete(Post::class)->wait(); - $this->searchService->delete(Comment::class)->wait(); - $this->searchService->delete(Tag::class)->wait(); + $this->searchService->delete(Post::class); + $this->searchService->delete(Comment::class); + $this->searchService->delete(Tag::class); } public function testDoctrineEventManagement(): void diff --git a/tests/TestCase/EngineTest.php b/tests/TestCase/EngineTest.php index 1bc24d5c..f0af3362 100644 --- a/tests/TestCase/EngineTest.php +++ b/tests/TestCase/EngineTest.php @@ -2,8 +2,8 @@ namespace Algolia\SearchBundle\TestCase; -use Algolia\AlgoliaSearch\Response\IndexingResponse; use Algolia\SearchBundle\BaseTest; +use Algolia\SearchBundle\Responses\NullResponse; use Algolia\SearchBundle\Engine; class EngineTest extends BaseTest @@ -41,23 +41,25 @@ public function testIndexing(): void 'autoGenerateObjectIDIfNotExist' => true, ]); self::assertArrayHasKey($searchablePost->getIndexName(), $result); - self::assertEquals(1, $result[$searchablePost->getIndexName()]->count()); + self::assertCount(1, $result[$searchablePost->getIndexName()][0]['objectIDs']); // Remove $result = $this->engine->remove($searchablePost, [ 'X-Forwarded-For' => '0.0.0.0', ]); self::assertArrayHasKey($searchablePost->getIndexName(), $result); - self::assertEquals(1, $result[$searchablePost->getIndexName()]->count()); + self::assertCount(1, $result[$searchablePost->getIndexName()][0]['objectIDs']); // Update $result = $this->engine->index($searchablePost, [ 'createIfNotExists' => true, ]); self::assertArrayHasKey($searchablePost->getIndexName(), $result); - self::assertEquals(1, $result[$searchablePost->getIndexName()]->count()); - foreach ($result as $indexName => $response) { - $response->wait(); + self::assertCount(1, $result[$searchablePost->getIndexName()][0]['objectIDs']); + foreach ($result as $indexName => $responses) { + foreach ($responses as $response) { + $this->get('search.client')->waitForTask($indexName, $response['taskID']); + } } // Search @@ -92,11 +94,11 @@ public function testIndexing(): void // Cleanup $result = $this->engine->clear($searchablePost->getIndexName(), []); - self::assertInstanceOf(IndexingResponse::class, $result); + self::assertNotInstanceOf(NullResponse::class, $result); // Delete index $result = $this->engine->delete($searchablePost->getIndexName(), []); - self::assertInstanceOf(IndexingResponse::class, $result); + self::assertNotInstanceOf(NullResponse::class, $result); } public function testIndexingEmptyEntity(): void diff --git a/tests/TestCase/SearchServiceTest.php b/tests/TestCase/SearchServiceTest.php index 475b5870..6ab1cc4a 100644 --- a/tests/TestCase/SearchServiceTest.php +++ b/tests/TestCase/SearchServiceTest.php @@ -25,9 +25,9 @@ public function setUp(): void public function cleanUp(): void { - $this->searchService->delete(Post::class)->wait(); - $this->searchService->delete(Comment::class)->wait(); - $this->searchService->delete(ContentAggregator::class)->wait(); + $this->searchService->delete(Post::class); + $this->searchService->delete(Comment::class); + $this->searchService->delete(ContentAggregator::class); } public function testIsSearchableMethod(): void diff --git a/tests/TestCase/SerializationTest.php b/tests/TestCase/SerializationTest.php index 07319bef..484c389a 100644 --- a/tests/TestCase/SerializationTest.php +++ b/tests/TestCase/SerializationTest.php @@ -21,7 +21,6 @@ public function testSerializerHasRequiredNormalizers(): void $refl = new \ReflectionClass($serializer); $normalizersProperty = $refl->getProperty('normalizers'); - $normalizersProperty->setAccessible(true); $normalizers = $normalizersProperty->getValue($serializer); $classes = array_map(static function ($value) { diff --git a/tests/TestCase/SettingsTest.php b/tests/TestCase/SettingsTest.php index e9ee1376..f096c9cd 100644 --- a/tests/TestCase/SettingsTest.php +++ b/tests/TestCase/SettingsTest.php @@ -2,7 +2,7 @@ namespace Algolia\SearchBundle\TestCase; -use Algolia\AlgoliaSearch\SearchClient; +use Algolia\AlgoliaSearch\Api\SearchClient; use Algolia\SearchBundle\BaseTest; use Algolia\SearchBundle\Settings\SettingsManager; use Algolia\SearchBundle\TestApp\Entity\Post; @@ -29,7 +29,7 @@ public function setUp(): void public function cleanUp(): void { - $this->get('search.service')->delete(Post::class)->wait(); + $this->get('search.service')->delete(Post::class); } /** @@ -42,8 +42,9 @@ public function testBackup(): void 'hitsPerPage' => 51, 'maxValuesPerFacet' => 99, ]; - $index = $this->client->initIndex($this->getPrefix() . $this->indexName); - $index->setSettings($settingsToUpdate)->wait(); + $fullIndexName = $this->getPrefix() . $this->indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); $message = $this->settingsManager->backup(['indices' => [$this->indexName]]); @@ -70,8 +71,9 @@ public function testBackupWithoutIndices(): void ]; foreach ($this->configIndexes as $indexName => $configIndex) { - $index = $this->client->initIndex($this->getPrefix() . $indexName); - $index->setSettings($settingsToUpdate)->wait(); + $fullIndexName = $this->getPrefix() . $indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); } $message = $this->settingsManager->backup(['indices' => []]); @@ -99,8 +101,9 @@ public function testPush(): void 'hitsPerPage' => 12, 'maxValuesPerFacet' => 100, ]; - $index = $this->client->initIndex($this->getPrefix() . $this->indexName); - $index->setSettings($settingsToUpdate)->wait(); + $fullIndexName = $this->getPrefix() . $this->indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); $message = $this->settingsManager->push(['indices' => [$this->indexName]]); @@ -112,9 +115,10 @@ public function testPush(): void for ($i = 0; $i < 5; $i++) { sleep(1); - $settings = $index->getSettings(); + $settings = $this->client->getSettings($fullIndexName); if (12 !== $settings['hitsPerPage']) { - self::assertEquals($savedSettings, $settings); + self::assertEquals($savedSettings['hitsPerPage'], $settings['hitsPerPage']); + self::assertEquals($savedSettings['maxValuesPerFacet'], $settings['maxValuesPerFacet']); } } } @@ -130,8 +134,9 @@ public function testPushWithoutIndices(): void ]; foreach ($this->configIndexes as $indexName => $configIndex) { - $index = $this->client->initIndex($this->getPrefix() . $indexName); - $index->setSettings($settingsToUpdate)->wait(); + $fullIndexName = $this->getPrefix() . $indexName; + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $this->client->waitForTask($fullIndexName, $response['taskID']); } $message = $this->settingsManager->push(['indices' => []]); @@ -143,11 +148,13 @@ public function testPushWithoutIndices(): void $this->getFileName($indexName, 'settings') ), true); + $fullIndexName = $this->getPrefix() . $indexName; for ($i = 0; $i < 5; $i++) { sleep(1); - $settings = $index->getSettings(); + $settings = $this->client->getSettings($fullIndexName); if (12 !== $settings['hitsPerPage']) { - self::assertEquals($savedSettings, $settings); + self::assertEquals($savedSettings['hitsPerPage'], $settings['hitsPerPage']); + self::assertEquals($savedSettings['maxValuesPerFacet'], $settings['maxValuesPerFacet']); } } } From a4ed70c0ed94b8f1df40740dc5608118d7278853 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 10:43:42 +0300 Subject: [PATCH 02/16] chore: applied php cs fixer --- src/Command/SearchImportCommand.php | 6 ++--- src/Engine.php | 24 +++++++++----------- src/Responses/SearchServiceResponse.php | 2 +- src/SearchService.php | 28 +++++++++-------------- src/Services/AlgoliaSearchService.php | 30 +++++++++---------------- src/Services/NullSearchService.php | 28 +++++++++-------------- tests/TestCase/CommandsTest.php | 8 +++---- tests/TestCase/EngineTest.php | 2 +- tests/TestCase/SerializationTest.php | 2 +- tests/TestCase/SettingsTest.php | 8 +++---- 10 files changed, 54 insertions(+), 84 deletions(-) diff --git a/src/Command/SearchImportCommand.php b/src/Command/SearchImportCommand.php index 8e529821..3459154c 100644 --- a/src/Command/SearchImportCommand.php +++ b/src/Command/SearchImportCommand.php @@ -82,9 +82,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $temporaryIndexName = $this->searchServiceForAtomicReindex->searchableAs($entityClassName); $output->writeln("Creating temporary index $temporaryIndexName"); $this->searchClient->operationIndex($sourceIndexName, [ - 'operation' => 'copy', + 'operation' => 'copy', 'destination' => $temporaryIndexName, - 'scope' => ['settings', 'synonyms', 'rules'], + 'scope' => ['settings', 'synonyms', 'rules'], ]); } @@ -130,7 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $output->writeln("Moving $indexName -> $sourceIndexName\n"); $this->searchClient->operationIndex($indexName, [ - 'operation' => 'move', + 'operation' => 'move', 'destination' => $sourceIndexName, ]); } diff --git a/src/Engine.php b/src/Engine.php index 698a6d55..62731e8f 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -32,7 +32,7 @@ public function getClient() * This method allows you to create records on your index by sending one or more objects. * Each object contains a set of attributes and values, which represents a full record on an index. * - * @param array $searchableEntities + * @param array $searchableEntities * @param array $requestOptions * * @return array @@ -77,7 +77,7 @@ public function index($searchableEntities, $requestOptions) * * This method enables you to remove one or more objects from an index. * - * @param array $searchableEntities + * @param array $searchableEntities * @param array $requestOptions * * @return array @@ -122,10 +122,8 @@ public function remove($searchableEntities, $requestOptions) * If you want to remove the entire index and not just its records, use the * delete method instead. * - * @param string $indexName + * @param string $indexName * @param array $requestOptions - * - * @return mixed */ public function clear($indexName, $requestOptions) { @@ -148,10 +146,8 @@ public function clear($indexName, $requestOptions) * If the index has replicas, they will be preserved but will no longer be * linked to their primary index. Instead, they'll become independent indices. * - * @param string $indexName + * @param string $indexName * @param array $requestOptions - * - * @return mixed */ public function delete($indexName, $requestOptions) { @@ -168,8 +164,8 @@ public function delete($indexName, $requestOptions) /** * Method used for querying an index. * - * @param string $query - * @param string $indexName + * @param string $query + * @param string $indexName * @param array $requestOptions * * @return array @@ -189,8 +185,8 @@ public function search($query, $indexName, $requestOptions) /** * Search the index and returns the objectIDs. * - * @param string $query - * @param string $indexName + * @param string $query + * @param string $indexName * @param array $requestOptions * * @return array @@ -209,8 +205,8 @@ public function searchIds($query, $indexName, $requestOptions) /** * Search the index and returns the number of results. * - * @param string $query - * @param string $indexName + * @param string $query + * @param string $indexName * @param array $requestOptions * * @return int diff --git a/src/Responses/SearchServiceResponse.php b/src/Responses/SearchServiceResponse.php index b34b3252..7fc61c9f 100644 --- a/src/Responses/SearchServiceResponse.php +++ b/src/Responses/SearchServiceResponse.php @@ -24,7 +24,7 @@ final class SearchServiceResponse implements \Iterator */ public function __construct(SearchClient $client, $apiResponse) { - $this->client = $client; + $this->client = $client; $this->apiResponse = $apiResponse; } diff --git a/src/SearchService.php b/src/SearchService.php index 85e07bf8..c55af650 100644 --- a/src/SearchService.php +++ b/src/SearchService.php @@ -33,40 +33,32 @@ public function getConfiguration(); public function searchableAs($className); /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []); /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []); /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function clear($className, $requestOptions = []); /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function delete($className, $requestOptions = []); /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -76,8 +68,8 @@ public function delete($className, $requestOptions = []); public function search(ObjectManager $objectManager, $className, $query = '', $requestOptions = []); /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -87,8 +79,8 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r public function rawSearch($className, $query = '', $requestOptions = []); /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return int diff --git a/src/Services/AlgoliaSearchService.php b/src/Services/AlgoliaSearchService.php index 888f6320..4c294c3b 100644 --- a/src/Services/AlgoliaSearchService.php +++ b/src/Services/AlgoliaSearchService.php @@ -119,10 +119,8 @@ public function searchableAs($className) } /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -151,10 +149,8 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption } /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -171,10 +167,8 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio } /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function clear($className, $requestOptions = []) { @@ -184,10 +178,8 @@ public function clear($className, $requestOptions = []) } /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function delete($className, $requestOptions = []) { @@ -197,8 +189,8 @@ public function delete($className, $requestOptions = []) } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -234,8 +226,8 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -250,8 +242,8 @@ public function rawSearch($className, $query = '', $requestOptions = []) } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return int @@ -390,8 +382,6 @@ private function setIndexIfMapping() * * @param array $entities * @param callable $operation - * - * @return mixed */ private function makeSearchServiceResponseFrom(ObjectManager $objectManager, array $entities, $operation) { diff --git a/src/Services/NullSearchService.php b/src/Services/NullSearchService.php index 9cf96708..27594052 100644 --- a/src/Services/NullSearchService.php +++ b/src/Services/NullSearchService.php @@ -51,10 +51,8 @@ public function searchableAs($className) } /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -62,10 +60,8 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption } /** - * @param object|array $searchables + * @param object|array $searchables * @param array $requestOptions - * - * @return mixed */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -73,10 +69,8 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio } /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function clear($className, $requestOptions = []) { @@ -84,10 +78,8 @@ public function clear($className, $requestOptions = []) } /** - * @param string $className + * @param string $className * @param array $requestOptions - * - * @return mixed */ public function delete($className, $requestOptions = []) { @@ -95,8 +87,8 @@ public function delete($className, $requestOptions = []) } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -111,8 +103,8 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return array @@ -135,8 +127,8 @@ public function rawSearch($className, $query = '', $requestOptions = []) } /** - * @param string $className - * @param string $query + * @param string $className + * @param string $query * @param array $requestOptions * * @return int diff --git a/tests/TestCase/CommandsTest.php b/tests/TestCase/CommandsTest.php index b831f66b..75bc602f 100644 --- a/tests/TestCase/CommandsTest.php +++ b/tests/TestCase/CommandsTest.php @@ -31,12 +31,12 @@ public function setUp(): void $this->indexName = 'posts'; $fullIndexName = $this->getPrefix() . $this->indexName; - $response = $this->client->setSettings($fullIndexName, $this->getDefaultConfig()); + $response = $this->client->setSettings($fullIndexName, $this->getDefaultConfig()); $this->client->waitForTask($fullIndexName, $response['taskID']); $contentsIndexName = 'contents'; $fullContentsName = $this->getPrefix() . $contentsIndexName; - $response = $this->client->setSettings($fullContentsName, $this->getDefaultConfig()); + $response = $this->client->setSettings($fullContentsName, $this->getDefaultConfig()); $this->client->waitForTask($fullContentsName, $response['taskID']); $this->application = new Application(self::$kernel); @@ -182,7 +182,7 @@ public function testSearchSettingsBackupCommand(): void 'maxValuesPerFacet' => 99, ]; $fullIndexName = $this->getPrefix() . $this->indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); $command = $this->application->find('search:settings:backup'); $commandTester = new CommandTester($command); @@ -209,7 +209,7 @@ public function testSearchSettingsPushCommand(): void 'maxValuesPerFacet' => 100, ]; $fullIndexName = $this->getPrefix() . $this->indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); $settings = $this->client->getSettings($fullIndexName); $settingsFile = $this->getFileName($this->indexName, 'settings'); diff --git a/tests/TestCase/EngineTest.php b/tests/TestCase/EngineTest.php index f0af3362..01b92711 100644 --- a/tests/TestCase/EngineTest.php +++ b/tests/TestCase/EngineTest.php @@ -3,8 +3,8 @@ namespace Algolia\SearchBundle\TestCase; use Algolia\SearchBundle\BaseTest; -use Algolia\SearchBundle\Responses\NullResponse; use Algolia\SearchBundle\Engine; +use Algolia\SearchBundle\Responses\NullResponse; class EngineTest extends BaseTest { diff --git a/tests/TestCase/SerializationTest.php b/tests/TestCase/SerializationTest.php index 484c389a..1e011148 100644 --- a/tests/TestCase/SerializationTest.php +++ b/tests/TestCase/SerializationTest.php @@ -21,7 +21,7 @@ public function testSerializerHasRequiredNormalizers(): void $refl = new \ReflectionClass($serializer); $normalizersProperty = $refl->getProperty('normalizers'); - $normalizers = $normalizersProperty->getValue($serializer); + $normalizers = $normalizersProperty->getValue($serializer); $classes = array_map(static function ($value) { return get_class($value); diff --git a/tests/TestCase/SettingsTest.php b/tests/TestCase/SettingsTest.php index f096c9cd..ad4124d7 100644 --- a/tests/TestCase/SettingsTest.php +++ b/tests/TestCase/SettingsTest.php @@ -43,7 +43,7 @@ public function testBackup(): void 'maxValuesPerFacet' => 99, ]; $fullIndexName = $this->getPrefix() . $this->indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); $message = $this->settingsManager->backup(['indices' => [$this->indexName]]); @@ -72,7 +72,7 @@ public function testBackupWithoutIndices(): void foreach ($this->configIndexes as $indexName => $configIndex) { $fullIndexName = $this->getPrefix() . $indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); } @@ -102,7 +102,7 @@ public function testPush(): void 'maxValuesPerFacet' => 100, ]; $fullIndexName = $this->getPrefix() . $this->indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); $message = $this->settingsManager->push(['indices' => [$this->indexName]]); @@ -135,7 +135,7 @@ public function testPushWithoutIndices(): void foreach ($this->configIndexes as $indexName => $configIndex) { $fullIndexName = $this->getPrefix() . $indexName; - $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); + $response = $this->client->setSettings($fullIndexName, $settingsToUpdate); $this->client->waitForTask($fullIndexName, $response['taskID']); } From 34560a897f726c2b74fb3101cc39505d6ab376a8 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 10:56:02 +0300 Subject: [PATCH 03/16] chore: replace array_map with array_column for codacy --- src/Engine.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index 62731e8f..ac5c1614 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -197,9 +197,7 @@ public function searchIds($query, $indexName, $requestOptions) { $result = $this->search($query, $indexName, $requestOptions); - return array_map(function ($hit) { - return $hit['objectID']; - }, $result['hits']); + return array_column($result['hits'], 'objectID'); } /** From 617a418088efe238bcad46651ab3bf41d9a10002 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 11:38:40 +0300 Subject: [PATCH 04/16] fix: forward requestOptions to v4 SDK calls and fix ArrayAccess compatibility --- src/Command/SearchImportCommand.php | 3 ++- src/Engine.php | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Command/SearchImportCommand.php b/src/Command/SearchImportCommand.php index 3459154c..1635e501 100644 --- a/src/Command/SearchImportCommand.php +++ b/src/Command/SearchImportCommand.php @@ -81,11 +81,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($shouldDoAtomicReindex) { $temporaryIndexName = $this->searchServiceForAtomicReindex->searchableAs($entityClassName); $output->writeln("Creating temporary index $temporaryIndexName"); - $this->searchClient->operationIndex($sourceIndexName, [ + $copyResponse = $this->searchClient->operationIndex($sourceIndexName, [ 'operation' => 'copy', 'destination' => $temporaryIndexName, 'scope' => ['settings', 'synonyms', 'rules'], ]); + $this->searchClient->waitForTask($sourceIndexName, $copyResponse['taskID']); } $allResponses = []; diff --git a/src/Engine.php b/src/Engine.php index ac5c1614..31507da8 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -66,7 +66,7 @@ public function index($searchableEntities, $requestOptions) $result = []; foreach ($data as $indexName => $objects) { - $result[$indexName] = $this->client->saveObjects($indexName, $objects); + $result[$indexName] = $this->client->saveObjects($indexName, $objects, false, 1000, $requestOptions); } return $result; @@ -107,7 +107,7 @@ public function remove($searchableEntities, $requestOptions) $result = []; foreach ($data as $indexName => $objects) { - $result[$indexName] = $this->client->deleteObjects($indexName, $objects); + $result[$indexName] = $this->client->deleteObjects($indexName, $objects, false, 1000, $requestOptions); } return $result; @@ -128,7 +128,7 @@ public function remove($searchableEntities, $requestOptions) public function clear($indexName, $requestOptions) { if ($this->client->indexExists($indexName)) { - $response = $this->client->clearObjects($indexName); + $response = $this->client->clearObjects($indexName, $requestOptions); $this->client->waitForTask($indexName, $response['taskID']); return $response; @@ -152,7 +152,7 @@ public function clear($indexName, $requestOptions) public function delete($indexName, $requestOptions) { if ($this->client->indexExists($indexName)) { - $response = $this->client->deleteIndex($indexName); + $response = $this->client->deleteIndex($indexName, $requestOptions); $this->client->waitForTask($indexName, $response['taskID']); return $response; @@ -197,7 +197,12 @@ public function searchIds($query, $indexName, $requestOptions) { $result = $this->search($query, $indexName, $requestOptions); - return array_column($result['hits'], 'objectID'); + $ids = []; + foreach ($result['hits'] as $hit) { + $ids[] = $hit['objectID']; + } + + return $ids; } /** From 326046f98c1e59a63d759e9ddfd23182e6a8934c Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 15:10:28 +0300 Subject: [PATCH 05/16] fix: preserve async clear/delete behavior from v3 and forward requestOptions to search --- src/Engine.php | 9 ++++----- src/Responses/EngineResponse.php | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 src/Responses/EngineResponse.php diff --git a/src/Engine.php b/src/Engine.php index 31507da8..d599b29b 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -3,6 +3,7 @@ namespace Algolia\SearchBundle; use Algolia\AlgoliaSearch\Api\SearchClient; +use Algolia\SearchBundle\Responses\EngineResponse; use Algolia\SearchBundle\Responses\NullResponse; /** @@ -129,9 +130,8 @@ public function clear($indexName, $requestOptions) { if ($this->client->indexExists($indexName)) { $response = $this->client->clearObjects($indexName, $requestOptions); - $this->client->waitForTask($indexName, $response['taskID']); - return $response; + return new EngineResponse($this->client, $indexName, $response['taskID']); } return new NullResponse(); @@ -153,9 +153,8 @@ public function delete($indexName, $requestOptions) { if ($this->client->indexExists($indexName)) { $response = $this->client->deleteIndex($indexName, $requestOptions); - $this->client->waitForTask($indexName, $response['taskID']); - return $response; + return new EngineResponse($this->client, $indexName, $response['taskID']); } return new NullResponse(); @@ -179,7 +178,7 @@ public function search($query, $indexName, $requestOptions) is_array($requestOptions) ? $requestOptions : [] ); - return $this->client->searchSingleIndex($indexName, $searchParams); + return $this->client->searchSingleIndex($indexName, $searchParams, $requestOptions); } /** diff --git a/src/Responses/EngineResponse.php b/src/Responses/EngineResponse.php new file mode 100644 index 00000000..e6c21837 --- /dev/null +++ b/src/Responses/EngineResponse.php @@ -0,0 +1,32 @@ +client = $client; + $this->indexName = $indexName; + $this->taskID = $taskID; + } + + public function wait() + { + $this->client->waitForTask($this->indexName, $this->taskID); + } +} From be4bbfde5d4746ddb4e419d33cfc5a331d290f50 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 15:50:47 +0300 Subject: [PATCH 06/16] fix: modified AlgoliaAgent clientName --- src/AlgoliaSearchBundle.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AlgoliaSearchBundle.php b/src/AlgoliaSearchBundle.php index e3892e3a..21f5253c 100644 --- a/src/AlgoliaSearchBundle.php +++ b/src/AlgoliaSearchBundle.php @@ -17,7 +17,7 @@ public function boot(): void { parent::boot(); - AlgoliaAgent::addAlgoliaAgent('SearchClient', 'Symfony Search Bundle', self::VERSION); - AlgoliaAgent::addAlgoliaAgent('SearchClient', 'Symfony', SfKernel::VERSION); + AlgoliaAgent::addAlgoliaAgent('Search', 'Symfony Search Bundle', self::VERSION); + AlgoliaAgent::addAlgoliaAgent('Search', 'Symfony', SfKernel::VERSION); } } From e2cb908d1086bf13b91b7a8f37513a2ae66ef6e2 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 15:55:50 +0300 Subject: [PATCH 07/16] fix: separate search params from http options --- src/Engine.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index d599b29b..f5fe449c 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -173,12 +173,20 @@ public function delete($indexName, $requestOptions) */ public function search($query, $indexName, $requestOptions) { - $searchParams = array_merge( - ['query' => $query], - is_array($requestOptions) ? $requestOptions : [] - ); + $httpOptionKeys = ['headers', 'queryParameters', 'body', 'readTimeout', 'writeTimeout', 'connectTimeout']; - return $this->client->searchSingleIndex($indexName, $searchParams, $requestOptions); + $searchParams = ['query' => $query]; + $httpOptions = []; + + foreach ($requestOptions as $key => $value) { + if (in_array($key, $httpOptionKeys, true)) { + $httpOptions[$key] = $value; + } else { + $searchParams[$key] = $value; + } + } + + return $this->client->searchSingleIndex($indexName, $searchParams, $httpOptions); } /** From 172f774b25290dedc0ee9bc0da86ccf4307bdb7e Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 15:58:51 +0300 Subject: [PATCH 08/16] fix: lint --- src/Engine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine.php b/src/Engine.php index f5fe449c..a1dad220 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -176,7 +176,7 @@ public function search($query, $indexName, $requestOptions) $httpOptionKeys = ['headers', 'queryParameters', 'body', 'readTimeout', 'writeTimeout', 'connectTimeout']; $searchParams = ['query' => $query]; - $httpOptions = []; + $httpOptions = []; foreach ($requestOptions as $key => $value) { if (in_array($key, $httpOptionKeys, true)) { From 3033845575f725ce591d2644bcdd72fa8945661b Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 16:18:55 +0300 Subject: [PATCH 09/16] fix: added wait back on test cleanups --- tests/TestCase/CommandsTest.php | 6 +++--- tests/TestCase/DoctrineTest.php | 6 +++--- tests/TestCase/SearchServiceTest.php | 12 ++++++------ tests/TestCase/SettingsTest.php | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/TestCase/CommandsTest.php b/tests/TestCase/CommandsTest.php index 75bc602f..eb086ff7 100644 --- a/tests/TestCase/CommandsTest.php +++ b/tests/TestCase/CommandsTest.php @@ -45,9 +45,9 @@ public function setUp(): void public function cleanUp(): void { - $this->searchService->delete(Post::class); - $this->searchService->delete(Comment::class); - $this->searchService->delete(ContentAggregator::class); + $this->searchService->delete(Post::class)->wait(); + $this->searchService->delete(Comment::class)->wait(); + $this->searchService->delete(ContentAggregator::class)->wait(); } public function testSearchClearUnknownIndex(): void diff --git a/tests/TestCase/DoctrineTest.php b/tests/TestCase/DoctrineTest.php index b7c324e8..7ee5172a 100644 --- a/tests/TestCase/DoctrineTest.php +++ b/tests/TestCase/DoctrineTest.php @@ -31,9 +31,9 @@ public function setUp(): void public function cleanUp(): void { - $this->searchService->delete(Post::class); - $this->searchService->delete(Comment::class); - $this->searchService->delete(Tag::class); + $this->searchService->delete(Post::class)->wait(); + $this->searchService->delete(Comment::class)->wait(); + $this->searchService->delete(Tag::class)->wait(); } public function testDoctrineEventManagement(): void diff --git a/tests/TestCase/SearchServiceTest.php b/tests/TestCase/SearchServiceTest.php index 6ab1cc4a..27eb3306 100644 --- a/tests/TestCase/SearchServiceTest.php +++ b/tests/TestCase/SearchServiceTest.php @@ -25,9 +25,9 @@ public function setUp(): void public function cleanUp(): void { - $this->searchService->delete(Post::class); - $this->searchService->delete(Comment::class); - $this->searchService->delete(ContentAggregator::class); + $this->searchService->delete(Post::class)->wait(); + $this->searchService->delete(Comment::class)->wait(); + $this->searchService->delete(ContentAggregator::class)->wait(); } public function testIsSearchableMethod(): void @@ -108,9 +108,9 @@ public function testIndexedDataAreSearchable(): void self::assertEquals(6, $this->searchService->count(ContentAggregator::class)); // Cleanup - $this->searchService->delete(Post::class); - $this->searchService->delete(Comment::class); - $this->searchService->delete(ContentAggregator::class); + $this->searchService->delete(Post::class)->wait(); + $this->searchService->delete(Comment::class)->wait(); + $this->searchService->delete(ContentAggregator::class)->wait(); $this->cleanUp(); } diff --git a/tests/TestCase/SettingsTest.php b/tests/TestCase/SettingsTest.php index ad4124d7..22cd33c1 100644 --- a/tests/TestCase/SettingsTest.php +++ b/tests/TestCase/SettingsTest.php @@ -29,7 +29,7 @@ public function setUp(): void public function cleanUp(): void { - $this->get('search.service')->delete(Post::class); + $this->get('search.service')->delete(Post::class)->wait(); } /** From 049291120bb99e93253739c6b2bece3d0c0aa8f9 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Tue, 31 Mar 2026 17:14:54 +0300 Subject: [PATCH 10/16] add upgrade doc and changelog entry --- CHANGELOG.md | 7 ++- UPGRADE-8.1.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 UPGRADE-8.1.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 721122a2..ce432625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p # Release Notes -## [Unreleased](https://github.com/algolia/search-bundle/compare/8.0.0...master) +## [v8.1.0](https://github.com/algolia/search-bundle/compare/8.0.0...8.1.0) + +### Breaking Changes +- **Upgraded Algolia PHP API client from v3 to v4** +- `SearchClient` namespace changed from `Algolia\AlgoliaSearch\SearchClient` to `Algolia\AlgoliaSearch\Api\SearchClient` — update any type-hints, autowiring, and DI configuration +- `$requestOptions` flat keys (e.g. custom HTTP headers as top-level keys) are no longer interpreted — use the structured format (`['headers' => [...]]`) ## [v8.0.0](https://github.com/algolia/search-bundle/compare/7.0.0...8.0.0) diff --git a/UPGRADE-8.1.md b/UPGRADE-8.1.md new file mode 100644 index 00000000..31ab5fd9 --- /dev/null +++ b/UPGRADE-8.1.md @@ -0,0 +1,123 @@ + +## Upgrading from 8.0.0 to 8.1.0 + +The SearchBundle now uses the [Algolia PHP API Client v4](https://www.algolia.com/doc/libraries/php/v4/). This is a significant upgrade of the underlying SDK that introduces a couple of breaking changes. + +Upgrade your `algolia/search-bundle` dependency to `^8.1` in your `composer.json` file and run `composer update algolia/search-bundle algolia/algoliasearch-client-php` in your terminal. + + +## SearchClient namespace + +The `SearchClient` class has moved to a new namespace. If you inject or type-hint `SearchClient` in your own services, update the fully qualified class name. + +Before: +```php +use Algolia\AlgoliaSearch\SearchClient; +``` + +After: +```php +use Algolia\AlgoliaSearch\Api\SearchClient; +``` + +The bundle's DI alias has been updated accordingly: + +Before: +```yaml +services: + App\Service\MyService: + arguments: + $client: '@Algolia\AlgoliaSearch\SearchClient' +``` + +After: +```yaml +services: + App\Service\MyService: + arguments: + $client: '@Algolia\AlgoliaSearch\Api\SearchClient' +``` + +If you autowire by type-hint, update your constructor or method signatures: + +Before: +```php +public function __construct(\Algolia\AlgoliaSearch\SearchClient $client) {} +``` + +After: +```php +public function __construct(\Algolia\AlgoliaSearch\Api\SearchClient $client) {} +``` + + +## Request options format change + +In v3, custom HTTP headers could be passed as flat top-level keys in `$requestOptions`. In v4, flat keys are **silently ignored**. HTTP headers must be nested under the `headers` key. + +Before: +```php +$searchService->index($em, $entity, ['X-Forwarded-For' => '0.0.0.0']); +``` + +After: +```php +$searchService->index($em, $entity, [ + 'headers' => ['X-Forwarded-For' => '0.0.0.0'], +]); +``` + +This applies to all `SearchService` methods that accept `$requestOptions`: `index()`, `remove()`, `clear()`, `delete()`, `search()`, `rawSearch()`, and `count()`. + +The recognized top-level keys for `$requestOptions` in v4 are: + +- `headers` — Custom HTTP headers (associative array) +- `queryParameters` — Extra URL query parameters (associative array) +- `body` — Extra body parameters (associative array) +- `readTimeout` — Read timeout in milliseconds (int) +- `writeTimeout` — Write timeout in milliseconds (int) +- `connectTimeout` — Connection timeout in milliseconds (int) + + +## List of changes in fully qualified namespaces + +
+ + + + + + + + + + + + + + + +
8.0.0Breaking Change8.1.0
Algolia\AlgoliaSearch\SearchClientMovedAlgolia\AlgoliaSearch\Api\SearchClient
+
+ + +## List of updated public services names + +
+ + + + + + + + + + + + + + + +
8.0.0Breaking Change8.1.0
Algolia\AlgoliaSearch\SearchClient (autowiring alias)RenamedAlgolia\AlgoliaSearch\Api\SearchClient
+
From 94fbf85dad6ade24f89ad961ecddcf5a69c074bb Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Wed, 1 Apr 2026 12:44:50 +0300 Subject: [PATCH 11/16] fix: atomic reindex moves wrong temp index when entity has aggregators --- src/Command/SearchImportCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Command/SearchImportCommand.php b/src/Command/SearchImportCommand.php index 1635e501..ba6f4aa8 100644 --- a/src/Command/SearchImportCommand.php +++ b/src/Command/SearchImportCommand.php @@ -124,13 +124,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manager->clear(); } - if ($shouldDoAtomicReindex && isset($indexName)) { + if ($shouldDoAtomicReindex && isset($temporaryIndexName)) { $output->writeln("Waiting for indexing tasks to finalize\n"); foreach ($allResponses as $response) { $response->wait(); } - $output->writeln("Moving $indexName -> $sourceIndexName\n"); - $this->searchClient->operationIndex($indexName, [ + $output->writeln("Moving $temporaryIndexName -> $sourceIndexName\n"); + $this->searchClient->operationIndex($temporaryIndexName, [ 'operation' => 'move', 'destination' => $sourceIndexName, ]); From a3cc749659a231261a52834cee85299381d81882 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Wed, 1 Apr 2026 12:51:02 +0300 Subject: [PATCH 12/16] fix: remove unnecessary isset --- src/Command/SearchImportCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/SearchImportCommand.php b/src/Command/SearchImportCommand.php index ba6f4aa8..d2a6f67b 100644 --- a/src/Command/SearchImportCommand.php +++ b/src/Command/SearchImportCommand.php @@ -124,7 +124,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $manager->clear(); } - if ($shouldDoAtomicReindex && isset($temporaryIndexName)) { + if ($shouldDoAtomicReindex) { $output->writeln("Waiting for indexing tasks to finalize\n"); foreach ($allResponses as $response) { $response->wait(); From ab67513db99fbd8b27b235db7d185e95d5db1069 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Wed, 1 Apr 2026 13:28:11 +0300 Subject: [PATCH 13/16] fix: use RequestOptions class --- src/Engine.php | 35 ++++++++++++++------------- src/SearchService.php | 29 +++++++++++----------- src/Services/AlgoliaSearchService.php | 35 ++++++++++++++------------- src/Services/NullSearchService.php | 35 ++++++++++++++------------- 4 files changed, 69 insertions(+), 65 deletions(-) diff --git a/src/Engine.php b/src/Engine.php index a1dad220..42ad3acb 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -3,6 +3,7 @@ namespace Algolia\SearchBundle; use Algolia\AlgoliaSearch\Api\SearchClient; +use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Algolia\SearchBundle\Responses\EngineResponse; use Algolia\SearchBundle\Responses\NullResponse; @@ -33,8 +34,8 @@ public function getClient() * This method allows you to create records on your index by sending one or more objects. * Each object contains a set of attributes and values, which represents a full record on an index. * - * @param array $searchableEntities - * @param array $requestOptions + * @param array $searchableEntities + * @param array|RequestOptions $requestOptions * * @return array * @@ -78,8 +79,8 @@ public function index($searchableEntities, $requestOptions) * * This method enables you to remove one or more objects from an index. * - * @param array $searchableEntities - * @param array $requestOptions + * @param array $searchableEntities + * @param array|RequestOptions $requestOptions * * @return array * @@ -123,8 +124,8 @@ public function remove($searchableEntities, $requestOptions) * If you want to remove the entire index and not just its records, use the * delete method instead. * - * @param string $indexName - * @param array $requestOptions + * @param string $indexName + * @param array|RequestOptions $requestOptions */ public function clear($indexName, $requestOptions) { @@ -146,8 +147,8 @@ public function clear($indexName, $requestOptions) * If the index has replicas, they will be preserved but will no longer be * linked to their primary index. Instead, they'll become independent indices. * - * @param string $indexName - * @param array $requestOptions + * @param string $indexName + * @param array|RequestOptions $requestOptions */ public function delete($indexName, $requestOptions) { @@ -163,9 +164,9 @@ public function delete($indexName, $requestOptions) /** * Method used for querying an index. * - * @param string $query - * @param string $indexName - * @param array $requestOptions + * @param string $query + * @param string $indexName + * @param array|RequestOptions $requestOptions * * @return array * @@ -192,9 +193,9 @@ public function search($query, $indexName, $requestOptions) /** * Search the index and returns the objectIDs. * - * @param string $query - * @param string $indexName - * @param array $requestOptions + * @param string $query + * @param string $indexName + * @param array|RequestOptions $requestOptions * * @return array * @@ -215,9 +216,9 @@ public function searchIds($query, $indexName, $requestOptions) /** * Search the index and returns the number of results. * - * @param string $query - * @param string $indexName - * @param array $requestOptions + * @param string $query + * @param string $indexName + * @param array|RequestOptions $requestOptions * * @return int * diff --git a/src/SearchService.php b/src/SearchService.php index c55af650..5019d317 100644 --- a/src/SearchService.php +++ b/src/SearchService.php @@ -2,6 +2,7 @@ namespace Algolia\SearchBundle; +use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Doctrine\Persistence\ObjectManager; interface SearchService @@ -33,33 +34,33 @@ public function getConfiguration(); public function searchableAs($className); /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []); /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []); /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function clear($className, $requestOptions = []); /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function delete($className, $requestOptions = []); /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * @@ -68,9 +69,9 @@ public function delete($className, $requestOptions = []); public function search(ObjectManager $objectManager, $className, $query = '', $requestOptions = []); /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * diff --git a/src/Services/AlgoliaSearchService.php b/src/Services/AlgoliaSearchService.php index 4c294c3b..7df35fe8 100644 --- a/src/Services/AlgoliaSearchService.php +++ b/src/Services/AlgoliaSearchService.php @@ -2,6 +2,7 @@ namespace Algolia\SearchBundle\Services; +use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Algolia\SearchBundle\Engine; use Algolia\SearchBundle\Entity\Aggregator; use Algolia\SearchBundle\Responses\SearchServiceResponse; @@ -119,8 +120,8 @@ public function searchableAs($className) } /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -149,8 +150,8 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption } /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -167,8 +168,8 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio } /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function clear($className, $requestOptions = []) { @@ -178,8 +179,8 @@ public function clear($className, $requestOptions = []) } /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function delete($className, $requestOptions = []) { @@ -189,9 +190,9 @@ public function delete($className, $requestOptions = []) } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * @@ -226,9 +227,9 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * @@ -242,9 +243,9 @@ public function rawSearch($className, $query = '', $requestOptions = []) } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return int * diff --git a/src/Services/NullSearchService.php b/src/Services/NullSearchService.php index 27594052..2cb425cc 100644 --- a/src/Services/NullSearchService.php +++ b/src/Services/NullSearchService.php @@ -2,6 +2,7 @@ namespace Algolia\SearchBundle\Services; +use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Algolia\SearchBundle\Responses\NullResponse; use Algolia\SearchBundle\SearchService; use Doctrine\Persistence\ObjectManager; @@ -51,8 +52,8 @@ public function searchableAs($className) } /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function index(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -60,8 +61,8 @@ public function index(ObjectManager $objectManager, $searchables, $requestOption } /** - * @param object|array $searchables - * @param array $requestOptions + * @param object|array $searchables + * @param array|RequestOptions $requestOptions */ public function remove(ObjectManager $objectManager, $searchables, $requestOptions = []) { @@ -69,8 +70,8 @@ public function remove(ObjectManager $objectManager, $searchables, $requestOptio } /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function clear($className, $requestOptions = []) { @@ -78,8 +79,8 @@ public function clear($className, $requestOptions = []) } /** - * @param string $className - * @param array $requestOptions + * @param string $className + * @param array|RequestOptions $requestOptions */ public function delete($className, $requestOptions = []) { @@ -87,9 +88,9 @@ public function delete($className, $requestOptions = []) } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * @@ -103,9 +104,9 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return array * @@ -127,9 +128,9 @@ public function rawSearch($className, $query = '', $requestOptions = []) } /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return int * From e212dd8d35c7931d89b0616d6f7633bc11af0cc5 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Wed, 1 Apr 2026 14:40:42 +0300 Subject: [PATCH 14/16] fix: remove changelog entry and delete upgrade doc --- CHANGELOG.md | 7 --- UPGRADE-8.1.md | 123 ------------------------------------------------- 2 files changed, 130 deletions(-) delete mode 100644 UPGRADE-8.1.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ce432625..4dec7cde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p # Release Notes -## [v8.1.0](https://github.com/algolia/search-bundle/compare/8.0.0...8.1.0) - -### Breaking Changes -- **Upgraded Algolia PHP API client from v3 to v4** -- `SearchClient` namespace changed from `Algolia\AlgoliaSearch\SearchClient` to `Algolia\AlgoliaSearch\Api\SearchClient` — update any type-hints, autowiring, and DI configuration -- `$requestOptions` flat keys (e.g. custom HTTP headers as top-level keys) are no longer interpreted — use the structured format (`['headers' => [...]]`) - ## [v8.0.0](https://github.com/algolia/search-bundle/compare/7.0.0...8.0.0) ### Added diff --git a/UPGRADE-8.1.md b/UPGRADE-8.1.md deleted file mode 100644 index 31ab5fd9..00000000 --- a/UPGRADE-8.1.md +++ /dev/null @@ -1,123 +0,0 @@ - -## Upgrading from 8.0.0 to 8.1.0 - -The SearchBundle now uses the [Algolia PHP API Client v4](https://www.algolia.com/doc/libraries/php/v4/). This is a significant upgrade of the underlying SDK that introduces a couple of breaking changes. - -Upgrade your `algolia/search-bundle` dependency to `^8.1` in your `composer.json` file and run `composer update algolia/search-bundle algolia/algoliasearch-client-php` in your terminal. - - -## SearchClient namespace - -The `SearchClient` class has moved to a new namespace. If you inject or type-hint `SearchClient` in your own services, update the fully qualified class name. - -Before: -```php -use Algolia\AlgoliaSearch\SearchClient; -``` - -After: -```php -use Algolia\AlgoliaSearch\Api\SearchClient; -``` - -The bundle's DI alias has been updated accordingly: - -Before: -```yaml -services: - App\Service\MyService: - arguments: - $client: '@Algolia\AlgoliaSearch\SearchClient' -``` - -After: -```yaml -services: - App\Service\MyService: - arguments: - $client: '@Algolia\AlgoliaSearch\Api\SearchClient' -``` - -If you autowire by type-hint, update your constructor or method signatures: - -Before: -```php -public function __construct(\Algolia\AlgoliaSearch\SearchClient $client) {} -``` - -After: -```php -public function __construct(\Algolia\AlgoliaSearch\Api\SearchClient $client) {} -``` - - -## Request options format change - -In v3, custom HTTP headers could be passed as flat top-level keys in `$requestOptions`. In v4, flat keys are **silently ignored**. HTTP headers must be nested under the `headers` key. - -Before: -```php -$searchService->index($em, $entity, ['X-Forwarded-For' => '0.0.0.0']); -``` - -After: -```php -$searchService->index($em, $entity, [ - 'headers' => ['X-Forwarded-For' => '0.0.0.0'], -]); -``` - -This applies to all `SearchService` methods that accept `$requestOptions`: `index()`, `remove()`, `clear()`, `delete()`, `search()`, `rawSearch()`, and `count()`. - -The recognized top-level keys for `$requestOptions` in v4 are: - -- `headers` — Custom HTTP headers (associative array) -- `queryParameters` — Extra URL query parameters (associative array) -- `body` — Extra body parameters (associative array) -- `readTimeout` — Read timeout in milliseconds (int) -- `writeTimeout` — Write timeout in milliseconds (int) -- `connectTimeout` — Connection timeout in milliseconds (int) - - -## List of changes in fully qualified namespaces - -
- - - - - - - - - - - - - - - -
8.0.0Breaking Change8.1.0
Algolia\AlgoliaSearch\SearchClientMovedAlgolia\AlgoliaSearch\Api\SearchClient
-
- - -## List of updated public services names - -
- - - - - - - - - - - - - - - -
8.0.0Breaking Change8.1.0
Algolia\AlgoliaSearch\SearchClient (autowiring alias)RenamedAlgolia\AlgoliaSearch\Api\SearchClient
-
From d6e9dab6d1d494c7d7648793ce524446607e4017 Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Thu, 2 Apr 2026 11:07:52 +0300 Subject: [PATCH 15/16] fix: enforce sdk v4.41 for RequestOptions compat --- composer.json | 2 +- src/Engine.php | 9 +++- src/SearchService.php | 6 +-- tests/TestCase/EngineTest.php | 93 +++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 2adf62f3..cf8e3eed 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "prefer-stable": true, "require": { "php": ">= 8.2", - "algolia/algoliasearch-client-php": "^4.0", + "algolia/algoliasearch-client-php": "^4.41.0", "doctrine/event-manager": "^1.1 || ^2.0", "doctrine/persistence": "^2.1 || ^3.0 || ^4.0", "symfony/filesystem": "^7.0 || ^8.0", diff --git a/src/Engine.php b/src/Engine.php index 42ad3acb..af2e36bb 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -174,11 +174,16 @@ public function delete($indexName, $requestOptions) */ public function search($query, $indexName, $requestOptions) { - $httpOptionKeys = ['headers', 'queryParameters', 'body', 'readTimeout', 'writeTimeout', 'connectTimeout']; - $searchParams = ['query' => $query]; $httpOptions = []; + if ($requestOptions instanceof RequestOptions) { + $searchParams = array_merge($searchParams, $requestOptions->getBody()); + + return $this->client->searchSingleIndex($indexName, $searchParams, $requestOptions); + } + + $httpOptionKeys = ['headers', 'queryParameters', 'body', 'readTimeout', 'writeTimeout', 'connectTimeout']; foreach ($requestOptions as $key => $value) { if (in_array($key, $httpOptionKeys, true)) { $httpOptions[$key] = $value; diff --git a/src/SearchService.php b/src/SearchService.php index 5019d317..68dd75b8 100644 --- a/src/SearchService.php +++ b/src/SearchService.php @@ -80,9 +80,9 @@ public function search(ObjectManager $objectManager, $className, $query = '', $r public function rawSearch($className, $query = '', $requestOptions = []); /** - * @param string $className - * @param string $query - * @param array $requestOptions + * @param string $className + * @param string $query + * @param array|RequestOptions $requestOptions * * @return int * diff --git a/tests/TestCase/EngineTest.php b/tests/TestCase/EngineTest.php index 01b92711..fa267470 100644 --- a/tests/TestCase/EngineTest.php +++ b/tests/TestCase/EngineTest.php @@ -2,6 +2,7 @@ namespace Algolia\SearchBundle\TestCase; +use Algolia\AlgoliaSearch\RequestOptions\RequestOptions; use Algolia\SearchBundle\BaseTest; use Algolia\SearchBundle\Engine; use Algolia\SearchBundle\Responses\NullResponse; @@ -101,6 +102,98 @@ public function testIndexing(): void self::assertNotInstanceOf(NullResponse::class, $result); } + /** + * Same as testIndexing but passes RequestOptions objects instead of arrays. + * Verifies that RequestOptions work identically to plain arrays across + * all Engine methods (index, remove, search, searchIds, count, clear, delete). + * + * @group legacy + */ + public function testIndexingWithRequestOptions(): void + { + $searchablePost = $this->createSearchablePost(); + $indexName = $searchablePost->getIndexName(); + + // Delete index in case there is already something + $this->engine->delete($indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + + // Index + $result = $this->engine->index($searchablePost, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertArrayHasKey($indexName, $result); + self::assertCount(1, $result[$indexName][0]['objectIDs']); + + // Remove + $result = $this->engine->remove($searchablePost, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertArrayHasKey($indexName, $result); + self::assertCount(1, $result[$indexName][0]['objectIDs']); + + // Re-index for search tests + $result = $this->engine->index($searchablePost, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertArrayHasKey($indexName, $result); + foreach ($result as $name => $responses) { + foreach ($responses as $response) { + $this->get('search.client')->waitForTask($name, $response['taskID']); + } + } + + // Search — verify search params in body are forwarded correctly + $result = $this->engine->search('Test', $indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'readTimeout' => 30, + 'writeTimeout' => 30, 'connectTimeout' => 5, + 'body' => [ + 'page' => 0, + 'hitsPerPage' => 1, + 'attributesToRetrieve' => ['title'], + ], + ])); + self::assertArrayHasKey('hits', $result); + self::assertCount(1, $result['hits']); + self::assertEquals(0, $result['page']); + self::assertEquals(1, $result['hitsPerPage']); + self::assertArrayHasKey('title', $result['hits'][0]); + self::assertArrayNotHasKey('content', $result['hits'][0]); + + // Search IDs + $result = $this->engine->searchIds('This should not have results', $indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => ['page' => 1, 'hitsPerPage' => 20], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertEmpty($result); + + // Count + $result = $this->engine->count('Test', $indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertEquals(1, $result); + + // Clear + $result = $this->engine->clear($indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertNotInstanceOf(NullResponse::class, $result); + + // Delete + $result = $this->engine->delete($indexName, new RequestOptions([ + 'headers' => [], 'queryParameters' => [], 'body' => [], + 'readTimeout' => 30, 'writeTimeout' => 30, 'connectTimeout' => 5, + ])); + self::assertNotInstanceOf(NullResponse::class, $result); + } + public function testIndexingEmptyEntity(): void { $searchableImage = $this->createSearchableImage(); From 0ea0c9991c7cb13c58c5b21337563ee558dde4db Mon Sep 17 00:00:00 2001 From: eric-zaharia Date: Thu, 2 Apr 2026 11:49:33 +0300 Subject: [PATCH 16/16] fix: avoid double passing the body --- src/Engine.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Engine.php b/src/Engine.php index af2e36bb..30ff5a08 100644 --- a/src/Engine.php +++ b/src/Engine.php @@ -178,7 +178,9 @@ public function search($query, $indexName, $requestOptions) $httpOptions = []; if ($requestOptions instanceof RequestOptions) { - $searchParams = array_merge($searchParams, $requestOptions->getBody()); + $searchParams = array_merge($searchParams, $requestOptions->getBody()); + $requestOptions = clone $requestOptions; + $requestOptions->setBody([]); return $this->client->searchSingleIndex($indexName, $searchParams, $requestOptions); }