From efcd5923bc6a9e175eddf59fd319d737b75f6c89 Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Nov 2024 13:33:18 +0100 Subject: [PATCH 1/5] add elasticsearch support --- README.md | 33 ++------- composer.json | 5 +- doc/00_Installation.md | 2 + doc/03_Upgrade_Notes.md | 10 +++ doc/04_Opensearch.md | 31 +++++++++ doc/05_Elasticsearch.md | 31 +++++++++ src/AdvancedObjectSearchBundle.php | 2 + .../AdvancedObjectSearchExtension.php | 30 ++++++++- src/DependencyInjection/Configuration.php | 8 ++- src/Enum/ClientType.php | 26 +++++++ src/Resources/config/services.yml | 1 + src/Service.php | 67 ++++++++++++++++--- 12 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 doc/04_Opensearch.md create mode 100644 doc/05_Elasticsearch.md create mode 100644 src/Enum/ClientType.php diff --git a/README.md b/README.md index 50bea02..c2513f1 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,19 @@ title: Advanced Object Search --- -# Pimcore Advanced Object Search via OpenSearch +# Pimcore Advanced Object Search via OpenSearch or Elasticsearch Advanced Object Search bundle provides advanced object search in -Pimcore backend powered by OpenSearch. +Pimcore backend powered by search index technology (OpenSearch or Elasticsearch). ## Integration into Pimcore ### Installation and Configuration Follow [Installation instructions](./doc/00_Installation.md). -#### Configure OpenSearch Client -OpenSearch client configuration takes place via [Pimcore OpenSearch Client Bundle](https://github.com/pimcore/opensearch-client) and has two parts. -1) Configuring an OpenSearch client. -2) Define the client to be used by advanced object search. - -```yaml - -# Configure an OpenSearch client -pimcore_open_search_client: - clients: - default: - hosts: ['https://opensearch:9200'] - password: 'admin' - username: 'admin' - ssl_verification: false - - -# Define the client to be used by advanced object search -advanced_object_search: - client_name: default # default is default value here, just need to be specified when other client should be used. -``` - -If nothing is configured, a default client connecting to `localhost:9200` is used. - +#### Configure Search Client +Setup search client configuration in your Symfony configuration files (e.g. `config.yaml`). +See [OpenSearch Client Setup](./doc/04_Opensearch.md) or [Elasticsearch Client Setup](./doc/05_Elasticsearch.md) for more information. #### Configure Advanced Object Search Before starting, setup at least following configuration in symfony configuration tree: @@ -69,7 +48,7 @@ Following event listeners are called automatically ### Pimcore Console Functions in Pimcore console. -- `advanced-object-search:process-update-queue` --> processes whole update queue of es search index. +- `advanced-object-search:process-update-queue` --> processes whole update queue of search index. - `advanced-object-search:re-index` --> Reindex all data objects of given class. Does not delete index first or resets update queue. - `advanced-object-search:update-mapping` --> Deletes and recreates mapping of given classes. Resets update queue for given class. diff --git a/composer.json b/composer.json index fda746a..ec735f0 100644 --- a/composer.json +++ b/composer.json @@ -9,8 +9,9 @@ "minimum-stability": "dev", "require": { "handcraftedinthealps/elasticsearch-dsl": "^8.0", - "pimcore/opensearch-client": "^1.0.0", - "pimcore/pimcore": "^11.1", + "pimcore/opensearch-client": "^1.x-dev", + "pimcore/elasticsearch-client": "^1.x-dev", + "pimcore/pimcore": "11.x-dev", "symfony/config": "^6.2", "symfony/console": "^6.2", "symfony/dependency-injection": "^6.2", diff --git a/doc/00_Installation.md b/doc/00_Installation.md index 99b04cb..8b87494 100644 --- a/doc/00_Installation.md +++ b/doc/00_Installation.md @@ -4,6 +4,8 @@ This bundle is only supported on Pimcore Core Framework 11. +This bundle requires minimum version of OpenSearch 2.7. or Elasticsearch 8.0.0. + ::: ## Installation diff --git a/doc/03_Upgrade_Notes.md b/doc/03_Upgrade_Notes.md index 565d231..4319ccb 100644 --- a/doc/03_Upgrade_Notes.md +++ b/doc/03_Upgrade_Notes.md @@ -22,3 +22,13 @@ ### Upgrade to v6.0.0 - Removed Pimcore 10 support - Removed Elasticsearch support and added OpenSearch support (Kept ONGR Elasticsearch library as it is compatible with OpenSearch) + +### Upgrade to v6.1.0 +- Added support for Elasticsearch in parallel to Opensearch. Opensearch remains the default search technology. If you are using Elasticsearch, you need to update your symfony configuration as follows: +```yml +advanced_object_search: + client_name: default + client_type: 'elasticsearch' +``` +- Introduced new service alias `pimcore.advanced_object_search.search-client`. This will replace deprecated alias `pimcore.advanced_object_search.opensearch-client` which will be removed in the next major version. + The new service alias can be used to inject the search client into your services. This search client is an instance of `Pimcore\SearchClient\SearchClientInterface` which is a common interface for OpenSearch and Elasticsearch clients. diff --git a/doc/04_Opensearch.md b/doc/04_Opensearch.md new file mode 100644 index 0000000..cffcfbc --- /dev/null +++ b/doc/04_Opensearch.md @@ -0,0 +1,31 @@ +# OpenSearch Client Setup + +:::info + +This bundle requires minimum version of OpenSearch 2.7. + +::: + +Following configuration is required to set up OpenSearch. The OpenSearch client configuration takes place via [Pimcore Opensearch Client](https://github.com/pimcore/opensearch-client) and has two parts: +1) Configuring an OpenSearch client. +2) Define the client to be used by Advanced Object Search bundle. + +```yaml +# Configuring an OpenSearch client +pimcore_open_search_client: + clients: + default: + hosts: ['https://opensearch:9200'] + password: 'admin' + username: 'admin' + ssl_verification: false + + +# Define the client to be used by advanced object search +advanced_object_search: + client_name: default # default is default value here, just need to be specified when other client should be used. +``` + +If nothing is configured, a default client connecting to `localhost:9200` is used. + +For the further configuration of the client, please refer to the [Pimcore OpenSearch Client documentation](https://github.com/pimcore/opensearch-client/blob/1.x/doc/02_Configuration.md). \ No newline at end of file diff --git a/doc/05_Elasticsearch.md b/doc/05_Elasticsearch.md new file mode 100644 index 0000000..d292f00 --- /dev/null +++ b/doc/05_Elasticsearch.md @@ -0,0 +1,31 @@ +# Elasticsearch Client Setup + +:::info + +This bundle requires minimum version of Elasticsearch 8.0. + +::: + +Following configuration is required to set up Elasticsearch. The Elasticsearch client configuration takes place via [Pimcore Elasticsearch Client](https://github.com/pimcore/elasticsearch-client) and has two parts: +1) Configuring an Elasticsearch client. +2) Define the client to be used by Advanced Object Search bundle. + +```yaml +# Configuring an Elasticsearch client +pimcore_elasticsearch_client: + es_clients: + default: + hosts: ['elastic:9200'] + username: 'elastic' + password: 'somethingsecret' + logger_channel: 'pimcore.elasicsearch' + +# Define the client to be used by advanced object search +advanced_object_search: + client_name: default # default is default value here, just need to be specified when other client should be used. + client_type: 'elasticsearch' # default is 'openSearch' +``` + +If nothing is configured, a default client connecting to `localhost:9200` is used. + +For the further configuration of the client, please refer to the [Pimcore Elasticsearch Client documentation](https://github.com/pimcore/elasticsearch-client/blob/1.x/README.md). \ No newline at end of file diff --git a/src/AdvancedObjectSearchBundle.php b/src/AdvancedObjectSearchBundle.php index f654f2f..c946abd 100644 --- a/src/AdvancedObjectSearchBundle.php +++ b/src/AdvancedObjectSearchBundle.php @@ -15,6 +15,7 @@ namespace AdvancedObjectSearchBundle; +use Pimcore\Bundle\ElasticsearchClientBundle\PimcoreElasticsearchClientBundle; use Pimcore\Bundle\OpenSearchClientBundle\PimcoreOpenSearchClientBundle; use Pimcore\Bundle\SimpleBackendSearchBundle\PimcoreSimpleBackendSearchBundle; use Pimcore\Extension\Bundle\AbstractPimcoreBundle; @@ -103,6 +104,7 @@ public function getInstaller(): Installer public static function registerDependentBundles(BundleCollection $collection): void { + $collection->addBundle(new PimcoreElasticsearchClientBundle()); $collection->addBundle(new PimcoreOpenSearchClientBundle()); $collection->addBundle(new PimcoreSimpleBackendSearchBundle()); } diff --git a/src/DependencyInjection/AdvancedObjectSearchExtension.php b/src/DependencyInjection/AdvancedObjectSearchExtension.php index c774f98..927ed9e 100644 --- a/src/DependencyInjection/AdvancedObjectSearchExtension.php +++ b/src/DependencyInjection/AdvancedObjectSearchExtension.php @@ -15,8 +15,10 @@ namespace AdvancedObjectSearchBundle\DependencyInjection; +use AdvancedObjectSearchBundle\Enum\ClientType; use AdvancedObjectSearchBundle\Maintenance\UpdateQueueProcessor; use AdvancedObjectSearchBundle\Messenger\QueueHandler; +use RuntimeException; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; @@ -66,7 +68,16 @@ public function loadInternal(array $config, ContainerBuilder $container) $definition->setArgument('$messengerQueueActivated', $config['messenger_queue_processing']['activated']); $openSearchClientId = 'pimcore.open_search_client.' . $config['client_name']; - $container->setAlias('pimcore.advanced_object_search.opensearch-client', $openSearchClientId); + $container->setAlias('pimcore.advanced_object_search.opensearch-client', $openSearchClientId) + ->setDeprecated( + 'pimcore/advanced-object-search', + '6.1', + 'The "%alias_id%" service alias is deprecated and will be removed in version 7.0. ' . + 'Please use "pimcore.advanced_object_search.search-client" instead.' + ); + + $clientId = $this->getDefaultSearchClientId($config); + $container->setAlias('pimcore.advanced_object_search.search-client', $clientId); } /** @@ -83,4 +94,21 @@ public function prepend(ContainerBuilder $container) $loader->load('doctrine_migrations.yml'); } } + + /** + * @throws RuntimeException + */ + private function getDefaultSearchClientId(array $indexSettings): string + { + $clientType = $indexSettings['client_type']; + $clientName = $indexSettings['client_name']; + + return match ($clientType) { + ClientType::OPEN_SEARCH->value => 'pimcore.openSearch.custom_client.' . $clientName, + ClientType::ELASTIC_SEARCH->value => 'pimcore.elasticsearch.custom_client.' . $clientName, + default => throw new RuntimeException( + sprintf('Invalid client type: %s', $clientType) + ) + }; + } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a0beb4a..ce94698 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -15,6 +15,7 @@ namespace AdvancedObjectSearchBundle\DependencyInjection; +use AdvancedObjectSearchBundle\Enum\ClientType; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -41,9 +42,14 @@ public function getConfigTreeBuilder() ->info('Prefix for index names') ->end() ->scalarNode('client_name') - ->info('Name of OpenSearch client configuration to be used.') + ->info('Name of search client configuration to be used.') ->defaultValue('default') ->end() + ->enumNode('client_type') + ->info('Type of search client to be used.') + ->values([ClientType::OPEN_SEARCH->value, ClientType::ELASTIC_SEARCH->value]) + ->defaultValue(ClientType::ELASTIC_SEARCH->value) + ->end() ->arrayNode('index_configuration') ->info('Add mapping between data object type and service implementation for field definition adapter') ->children() diff --git a/src/Enum/ClientType.php b/src/Enum/ClientType.php new file mode 100644 index 0000000..2395833 --- /dev/null +++ b/src/Enum/ClientType.php @@ -0,0 +1,26 @@ +indexNamePrefix = $indexConfigService->getIndexNamePrefix(); } + // ToDo Remove this and inject SearchClientInterface directly in version 7.0 + public function setSearchClientInterface(SearchClientInterface $searchClient): void + { + $this->searchClient = $searchClient; + } + /** * returns field definition adapter for given field definition * @@ -270,7 +279,7 @@ function ($fieldProperties) { public function updateMapping(ClassDefinition $classDefinition) { if ($this->isExcludedClass($classDefinition->getName())) { - if ($this->openSearchClient->indices()->exists(['index' => $this->getIndexName($classDefinition->getName())])) { + if ($this->existsIndicesRequest(['index' => $this->getIndexName($classDefinition->getName())])) { try { $this->deleteIndex($classDefinition); } catch (Exception $e) { @@ -281,7 +290,7 @@ public function updateMapping(ClassDefinition $classDefinition) return true; } - if (!$this->openSearchClient->indices()->exists(['index' => $this->getIndexName($classDefinition->getName())])) { + if (!$this->existsIndicesRequest(['index' => $this->getIndexName($classDefinition->getName())])) { $this->createIndex($classDefinition); } @@ -315,7 +324,12 @@ public function updateMapping(ClassDefinition $classDefinition) protected function doUpdateMapping(ClassDefinition $classDefinition) { $mapping = $this->generateMapping($classDefinition); - $this->openSearchClient->indices()->putMapping($mapping); + if ($this->searchClient === null) { + $this->openSearchClient->indices()->putMapping($mapping); + return; + } + + $this->searchClient->putIndexMapping($mapping); } /** @@ -336,7 +350,7 @@ protected function createIndex(ClassDefinition $classDefinition) try { $this->logger->info("Creating index $indexName for class " . $classDefinition->getName()); - $this->openSearchClient->indices()->create([ + $this->doIndicesRequest('create', [ 'index' => $indexName, 'body' => [ 'settings' => [ @@ -366,7 +380,7 @@ public function deleteIndex(ClassDefinition $classDefinition): void { $indexName = $this->getIndexName($classDefinition->getName()); $this->logger->info("Deleting index $indexName for class " . $classDefinition->getName()); - $this->openSearchClient->indices()->delete(['index' => $indexName]); + $this->doIndicesRequest('delete', ['index' => $indexName]); } /** @@ -423,7 +437,7 @@ public function doUpdateIndexData(Concrete $object, $ignoreUpdateQueue = false) ]; try { - $indexDocument = $this->openSearchClient->get($params); + $indexDocument = $this->getClient()->get($params); $originalChecksum = $indexDocument['_source']['checksum'] ?? -1; } catch (Exception $e) { $this->logger->debug($e->getMessage()); @@ -433,9 +447,9 @@ public function doUpdateIndexData(Concrete $object, $ignoreUpdateQueue = false) $indexUpdateParams = $this->getIndexData($object); if ($indexUpdateParams['body']['checksum'] != $originalChecksum) { - $this->openSearchClient->index($indexUpdateParams); + $this->getClient()->index($indexUpdateParams); $this->logger->info('Updates es index for data object ' . $object->getId()); - $this->openSearchClient->index($indexUpdateParams); + $this->getClient()->index($indexUpdateParams); } else { $this->logger->info('Not updating index for data object ' . $object->getId() . ' - nothing has changed.'); } @@ -482,7 +496,7 @@ public function doDeleteFromIndex(Concrete $object): void ]; $this->logger->info('Deleting data object ' . $object->getId() . ' from es index.'); - $this->openSearchClient->delete($params); + $this->getClient()->delete($params); } /** @@ -734,7 +748,7 @@ public function getIdsFromFilterNoLimit($classId, array $filters, $fullTextQuery $ids = []; do { - $results = $this->openSearchClient->search($params); + $results = $this->getClient()->search($params); $total = $results['hits']['total']; $searchAfter = end($results['hits']['hits'])['sort']; $search->setSearchAfter($searchAfter); @@ -803,7 +817,7 @@ public function doFilter($classId, array $filters, $fullTextQuery, $from = null, $this->logger->info('Filter-Params: ' . json_encode($params)); - return $this->openSearchClient->search($params); + return $this->getClient()->search($params); } /** @@ -877,4 +891,35 @@ protected function isExcludedField(string $className, string $fieldName): bool return isset($excludeFields[$className]) && in_array($fieldName, $excludeFields[$className]); } + + // ToDo Remove this and use SearchClientInterface directly in version 7.0 + private function getClient(): SearchClientInterface|OpenSearchClient + { + if ($this->searchClient !== null) { + return $this->searchClient; + } + + return $this->openSearchClient; + } + + // ToDo Remove this and use SearchClientInterface directly in version 7.0 + private function existsIndicesRequest(array $params): bool + { + if ($this->searchClient === null) { + return $this->openSearchClient->indices()->exists($params); + } + + return $this->searchClient->existsIndex($params); + } + + // ToDo Remove this and use SearchClientInterface directly in version 7.0 + private function doIndicesRequest(string $method, array $params): void + { + if ($this->searchClient === null) { + $this->openSearchClient->indices()->$method($params); + } + + $method .= 'Index'; + $this->searchClient->$method($params); + } } From d2e6af068adc356ce6feb4702360eb016c9cff0a Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Nov 2024 12:33:44 +0000 Subject: [PATCH 2/5] Apply php-cs-fixer changes --- src/Service.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service.php b/src/Service.php index 82b9bec..f4a93a8 100644 --- a/src/Service.php +++ b/src/Service.php @@ -326,6 +326,7 @@ protected function doUpdateMapping(ClassDefinition $classDefinition) $mapping = $this->generateMapping($classDefinition); if ($this->searchClient === null) { $this->openSearchClient->indices()->putMapping($mapping); + return; } @@ -893,7 +894,7 @@ protected function isExcludedField(string $className, string $fieldName): bool } // ToDo Remove this and use SearchClientInterface directly in version 7.0 - private function getClient(): SearchClientInterface|OpenSearchClient + private function getClient(): SearchClientInterface | OpenSearchClient { if ($this->searchClient !== null) { return $this->searchClient; From d59957896d09e211601d8a4a14140fcb0c51b6cc Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Nov 2024 14:21:54 +0100 Subject: [PATCH 3/5] fix: use factory for service --- .../AdvancedObjectSearchExtension.php | 19 ++--- .../OpenSearch/LegacyServiceFactory.php | 78 +++++++++++++++++++ src/Resources/config/services.yml | 8 +- 3 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 src/Factory/OpenSearch/LegacyServiceFactory.php diff --git a/src/DependencyInjection/AdvancedObjectSearchExtension.php b/src/DependencyInjection/AdvancedObjectSearchExtension.php index 927ed9e..436a443 100644 --- a/src/DependencyInjection/AdvancedObjectSearchExtension.php +++ b/src/DependencyInjection/AdvancedObjectSearchExtension.php @@ -66,15 +66,16 @@ public function loadInternal(array $config, ContainerBuilder $container) $definition = $container->getDefinition(UpdateQueueProcessor::class); $definition->setArgument('$messengerQueueActivated', $config['messenger_queue_processing']['activated']); - - $openSearchClientId = 'pimcore.open_search_client.' . $config['client_name']; - $container->setAlias('pimcore.advanced_object_search.opensearch-client', $openSearchClientId) - ->setDeprecated( - 'pimcore/advanced-object-search', - '6.1', - 'The "%alias_id%" service alias is deprecated and will be removed in version 7.0. ' . - 'Please use "pimcore.advanced_object_search.search-client" instead.' - ); + if ($config['client_type'] === ClientType::OPEN_SEARCH->value) { + $openSearchClientId = 'pimcore.open_search_client.' . $config['client_name']; + $container->setAlias('pimcore.advanced_object_search.opensearch-client', $openSearchClientId) + ->setDeprecated( + 'pimcore/advanced-object-search', + '6.1', + 'The "%alias_id%" service alias is deprecated and will be removed in version 7.0. ' . + 'Please use "pimcore.advanced_object_search.search-client" instead.' + ); + } $clientId = $this->getDefaultSearchClientId($config); $container->setAlias('pimcore.advanced_object_search.search-client', $clientId); diff --git a/src/Factory/OpenSearch/LegacyServiceFactory.php b/src/Factory/OpenSearch/LegacyServiceFactory.php new file mode 100644 index 0000000..bd67c62 --- /dev/null +++ b/src/Factory/OpenSearch/LegacyServiceFactory.php @@ -0,0 +1,78 @@ +client instanceof OpenSearchClientInterface => $this->client->getOriginalClient(), + $this->client instanceof ElasticsearchClientInterface => ClientBuilder::create()->build(), + default => null, + }; + + if ($openSearchClient === null) { + throw new RuntimeException('No client found for OpenSearch'); + } + + $service = new Service( + $this->logger, + $this->userResolver, + $filterLocator, + $this->eventDispatcher, + $this->translator, + $this->indexConfigService, + $openSearchClient + ); + + $service->setSearchClientInterface($this->client); + + return $service; + } +} diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index ae04aa6..59cb2c3 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -26,16 +26,20 @@ services: tags: - { name: monolog.logger, channel: advanced_object_search.es } + bundle.advanced_object_search.service.legacy_factory: + class: AdvancedObjectSearchBundle\Factory\OpenSearch\LegacyServiceFactory + arguments: + $client: '@pimcore.advanced_object_search.search-client' + bundle.advanced_object_search.service: alias: AdvancedObjectSearchBundle\Service AdvancedObjectSearchBundle\Service: + factory: ['@bundle.advanced_object_search.service.legacy_factory', 'create'] arguments: $filterLocator: '@bundle.advanced_object_search.filter_locator' - $openSearchClient: '@pimcore.advanced_object_search.opensearch-client' calls: - [setCoreFieldsConfig, ['%advanced_object_search.core_fields_configuration%']] - - [setSearchClientInterface, ['@pimcore.advanced_object_search.search-client']] tags: - { name: monolog.logger, channel: pimcore_advanced_object_search } From a0e62c8de9f516c5752c792ec1e51e5d5f5f7c92 Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Nov 2024 14:22:53 +0100 Subject: [PATCH 4/5] fix: use opensearch as default --- src/DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index ce94698..c6bd372 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -48,7 +48,7 @@ public function getConfigTreeBuilder() ->enumNode('client_type') ->info('Type of search client to be used.') ->values([ClientType::OPEN_SEARCH->value, ClientType::ELASTIC_SEARCH->value]) - ->defaultValue(ClientType::ELASTIC_SEARCH->value) + ->defaultValue(ClientType::OPEN_SEARCH->value) ->end() ->arrayNode('index_configuration') ->info('Add mapping between data object type and service implementation for field definition adapter') From 3b6f4bd9fc19fd6d6268397c3986a234bc696e19 Mon Sep 17 00:00:00 2001 From: lukmzig Date: Thu, 21 Nov 2024 14:28:08 +0100 Subject: [PATCH 5/5] fix: STAN --- src/Factory/OpenSearch/LegacyServiceFactory.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Factory/OpenSearch/LegacyServiceFactory.php b/src/Factory/OpenSearch/LegacyServiceFactory.php index bd67c62..f561573 100644 --- a/src/Factory/OpenSearch/LegacyServiceFactory.php +++ b/src/Factory/OpenSearch/LegacyServiceFactory.php @@ -33,15 +33,15 @@ * @internal * @deprecated will be removed in version 7.0 */ -final readonly class LegacyServiceFactory +final class LegacyServiceFactory { public function __construct( - private LoggerInterface $logger, - private TokenStorageUserResolver $userResolver, - private EventDispatcherInterface $eventDispatcher, - private Translator $translator, - private IndexConfigService $indexConfigService, - private SearchClientInterface $client + private readonly LoggerInterface $logger, + private readonly TokenStorageUserResolver $userResolver, + private readonly EventDispatcherInterface $eventDispatcher, + private readonly Translator $translator, + private readonly IndexConfigService $indexConfigService, + private readonly SearchClientInterface $client ) {