vendor/ibexa/solr/src/bundle/DependencyInjection/IbexaSolrExtension.php line 301

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. namespace Ibexa\Bundle\Solr\DependencyInjection;
  7. use Ibexa\Bundle\Solr\ApiLoader\BoostFactorProviderFactory;
  8. use Ibexa\Bundle\Solr\ApiLoader\SolrEngineFactory;
  9. use Ibexa\Solr\FieldMapper\BoostFactorProvider;
  10. use Ibexa\Solr\Gateway\DistributionStrategy\CloudDistributionStrategy;
  11. use Ibexa\Solr\Gateway\UpdateSerializerInterface;
  12. use Ibexa\Solr\Handler;
  13. use Symfony\Component\Config\FileLocator;
  14. use Symfony\Component\DependencyInjection\ChildDefinition;
  15. use Symfony\Component\DependencyInjection\ContainerBuilder;
  16. use Symfony\Component\DependencyInjection\Definition;
  17. use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
  18. use Symfony\Component\DependencyInjection\Reference;
  19. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  20. /**
  21. * @phpstan-type SolrHttpClientConfigArray = array{timeout: int, max_retries: int}
  22. */
  23. class IbexaSolrExtension extends Extension
  24. {
  25. /**
  26. * Main Solr search handler service ID.
  27. *
  28. * @var string
  29. */
  30. public const ENGINE_ID = Handler::class;
  31. /**
  32. * Configured core gateway service ID.
  33. *
  34. * Not using service alias since alias can't be passed for decoration.
  35. *
  36. * @var string
  37. */
  38. public const GATEWAY_ID = 'ibexa.solr.gateway.native';
  39. /**
  40. * Configured core filter service ID.
  41. *
  42. * Not using service alias since alias can't be passed for decoration.
  43. *
  44. * @var string
  45. */
  46. public const CORE_FILTER_ID = 'ibexa.solr.core_filter.native';
  47. /**
  48. * Configured core endpoint resolver service ID.
  49. *
  50. * Not using service alias since alias can't be passed for decoration.
  51. *
  52. * @var string
  53. */
  54. public const ENDPOINT_RESOLVER_ID = 'ibexa.solr.gateway.endpoint_resolver.native';
  55. /**
  56. * Endpoint class.
  57. *
  58. * @var string
  59. */
  60. public const ENDPOINT_CLASS = 'Ibexa\\Solr\\Gateway\\Endpoint';
  61. /**
  62. * Endpoint service tag.
  63. *
  64. * @var string
  65. */
  66. public const ENDPOINT_TAG = 'ibexa.search.solr.endpoint';
  67. /**
  68. * @var string
  69. */
  70. public const BOOST_FACTOR_PROVIDER_ID = BoostFactorProvider::class;
  71. /**
  72. * @var string
  73. */
  74. public const STANDALONE_DISTRIBUTION_STRATEGY_ID = 'ibexa.solr.gateway.distribution_strategy.abstract_standalone';
  75. /**
  76. * @var string
  77. */
  78. public const CLOUD_DISTRIBUTION_STRATEGY_ID = CloudDistributionStrategy::class;
  79. public const GATEWAY_UPDATE_SERIALIZER_TAG = 'ibexa.solr.gateway.serializer.update';
  80. public function getAlias()
  81. {
  82. return 'ibexa_solr';
  83. }
  84. public function getServicePrefix(): string
  85. {
  86. return 'ibexa.solr';
  87. }
  88. /**
  89. * Loads a specific configuration.
  90. *
  91. * @param array $configs An array of configuration values
  92. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container A ContainerBuilder instance
  93. *
  94. * @throws \InvalidArgumentException When provided tag is not defined in this extension
  95. *
  96. * @api
  97. */
  98. public function load(array $configs, ContainerBuilder $container)
  99. {
  100. $configuration = $this->getConfiguration($configs, $container);
  101. $config = $this->processConfiguration($configuration, $configs);
  102. // Loading configuration from lib/Resources/config/container
  103. $loader = new YamlFileLoader(
  104. $container,
  105. new FileLocator(__DIR__ . '/../../lib/Resources/config/container')
  106. );
  107. $loader->load('solr.yml');
  108. $loader = new YamlFileLoader(
  109. $container,
  110. new FileLocator(__DIR__ . '/../Resources/config')
  111. );
  112. $loader->load('services.yml');
  113. $this->processConnectionConfiguration($container, $config);
  114. $container
  115. ->registerForAutoconfiguration(UpdateSerializerInterface::class)
  116. ->addTag(self::GATEWAY_UPDATE_SERIALIZER_TAG);
  117. }
  118. /**
  119. * Processes connection configuration by flattening connection parameters
  120. * and setting them to the container as parameters.
  121. *
  122. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  123. * @param array $config
  124. */
  125. protected function processConnectionConfiguration(ContainerBuilder $container, array $config)
  126. {
  127. $alias = $this->getServicePrefix();
  128. if (isset($config['default_connection'])) {
  129. $container->setParameter(
  130. "{$alias}.default_connection",
  131. $config['default_connection']
  132. );
  133. } elseif (!empty($config['connections'])) {
  134. reset($config['connections']);
  135. $container->setParameter(
  136. "{$alias}.default_connection",
  137. key($config['connections'])
  138. );
  139. }
  140. if (isset($config['version'])) {
  141. $container->setParameter(
  142. "{$alias}.version",
  143. $config['version']
  144. );
  145. }
  146. foreach ($config['connections'] as $name => $params) {
  147. $this->configureSearchServices($container, $name, $params);
  148. $this->configureBoostMap($container, $name, $params);
  149. $this->configureIndexingDepth($container, $name, $params);
  150. $container->setParameter("$alias.connection.$name", $params);
  151. }
  152. foreach ($config['endpoints'] as $name => $params) {
  153. $this->defineEndpoint($container, $name, $params);
  154. }
  155. // Search engine itself, for given connection name
  156. $searchEngineDef = $container->findDefinition(self::ENGINE_ID);
  157. $searchEngineDef->setFactory([new Reference(SolrEngineFactory::class), 'buildEngine']);
  158. // Factory for BoostFactorProvider uses mapping configured for the connection in use
  159. $boostFactorProviderDef = $container->findDefinition(self::BOOST_FACTOR_PROVIDER_ID);
  160. $boostFactorProviderDef->setFactory([new Reference(BoostFactorProviderFactory::class), 'buildService']);
  161. if (isset($config['http_client'])) {
  162. $this->configureHttpClient($container, $config['http_client']);
  163. }
  164. }
  165. /**
  166. * Creates needed search services for given connection name and parameters.
  167. *
  168. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  169. * @param string $connectionName
  170. * @param array $connectionParams
  171. */
  172. private function configureSearchServices(ContainerBuilder $container, $connectionName, $connectionParams)
  173. {
  174. $alias = $this->getServicePrefix();
  175. // Endpoint resolver
  176. $endpointResolverDefinition = new ChildDefinition(self::ENDPOINT_RESOLVER_ID);
  177. $endpointResolverDefinition->replaceArgument(0, $connectionParams['entry_endpoints']);
  178. $endpointResolverDefinition->replaceArgument(1, $connectionParams['mapping']['translations']);
  179. $endpointResolverDefinition->replaceArgument(2, $connectionParams['mapping']['default']);
  180. $endpointResolverDefinition->replaceArgument(3, $connectionParams['mapping']['main_translations']);
  181. $endpointResolverId = "$alias.connection.$connectionName.endpoint_resolver_id";
  182. $container->setDefinition($endpointResolverId, $endpointResolverDefinition);
  183. // Core filter
  184. $coreFilterDefinition = new ChildDefinition(self::CORE_FILTER_ID);
  185. $coreFilterDefinition->replaceArgument(0, new Reference($endpointResolverId));
  186. $coreFilterDefinition->addTag('ibexa.search.solr.core.filter', ['connection' => $connectionName]);
  187. $coreFilterId = "$alias.connection.$connectionName.core_filter_id";
  188. $container->setDefinition($coreFilterId, $coreFilterDefinition);
  189. // Distribution Strategy
  190. $distributionStrategyId = "$alias.connection.$connectionName.distribution_strategy";
  191. switch ($connectionParams['distribution_strategy']) {
  192. case 'standalone':
  193. $distributionStrategyDefinition = new ChildDefinition(self::STANDALONE_DISTRIBUTION_STRATEGY_ID);
  194. $distributionStrategyDefinition->setArgument(1, new Reference($endpointResolverId));
  195. break;
  196. case 'cloud':
  197. $distributionStrategyDefinition = new ChildDefinition(self::CLOUD_DISTRIBUTION_STRATEGY_ID);
  198. $distributionStrategyDefinition->setArgument(1, new Reference($endpointResolverId));
  199. break;
  200. default:
  201. throw new \RuntimeException('Unknown distribution strategy');
  202. }
  203. $container->setDefinition($distributionStrategyId, $distributionStrategyDefinition);
  204. // Gateway
  205. $gatewayDefinition = new ChildDefinition(self::GATEWAY_ID);
  206. $gatewayDefinition->replaceArgument('$endpointResolver', new Reference($endpointResolverId));
  207. $gatewayDefinition->replaceArgument('$distributionStrategy', new Reference($distributionStrategyId));
  208. $gatewayDefinition->addTag('ibexa.search.solr.gateway', ['connection' => $connectionName]);
  209. $gatewayId = "$alias.connection.$connectionName.gateway_id";
  210. $container->setDefinition($gatewayId, $gatewayDefinition);
  211. }
  212. /**
  213. * Creates boost factor map parameter for a given $connectionName.
  214. *
  215. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  216. * @param string $connectionName
  217. * @param array $connectionParams
  218. */
  219. private function configureBoostMap(ContainerBuilder $container, $connectionName, $connectionParams)
  220. {
  221. $alias = $this->getServicePrefix();
  222. $boostFactorMap = $this->buildBoostFactorMap($connectionParams['boost_factors']);
  223. $boostFactorMapId = "{$alias}.connection.{$connectionName}.boost_factor_map_id";
  224. $container->setParameter($boostFactorMapId, $boostFactorMap);
  225. }
  226. /**
  227. * Creates indexing depth map parameter for a given $connectionName.
  228. *
  229. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  230. * @param string $connectionName
  231. * @param array $connectionParams
  232. */
  233. private function configureIndexingDepth(ContainerBuilder $container, $connectionName, $connectionParams)
  234. {
  235. $alias = $this->getServicePrefix();
  236. $defaultIndexingDepthId = "{$alias}.connection.{$connectionName}.indexing_depth.default";
  237. $contentTypeIndexingDepthMapId = "{$alias}.connection.{$connectionName}.indexing_depth.map";
  238. $container->setParameter($defaultIndexingDepthId, $connectionParams['indexing_depth']['default']);
  239. $container->setParameter($contentTypeIndexingDepthMapId, $connectionParams['indexing_depth']['content_type']);
  240. }
  241. /**
  242. * Creates Endpoint definition in the service container.
  243. *
  244. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  245. * @param string $alias
  246. * @param array $params
  247. */
  248. protected function defineEndpoint(ContainerBuilder $container, $alias, $params)
  249. {
  250. $definition = new Definition(self::ENDPOINT_CLASS, [$params]);
  251. $definition->addTag(self::ENDPOINT_TAG, ['alias' => $alias]);
  252. $container->setDefinition(
  253. sprintf($this->getServicePrefix() . '.endpoints.%s', $alias),
  254. $definition
  255. );
  256. }
  257. public function getConfiguration(array $config, ContainerBuilder $container)
  258. {
  259. return new Configuration($this->getAlias());
  260. }
  261. /**
  262. * Builds boost factor map from the given $config.
  263. *
  264. * @see \Ibexa\Solr\FieldMapper\BoostFactorProvider::$map
  265. *
  266. * @param array $config
  267. *
  268. * @return array
  269. */
  270. protected function buildBoostFactorMap(array $config)
  271. {
  272. $boostFactorMap = [];
  273. foreach ($config['content_type'] as $typeIdentifier => $factor) {
  274. $boostFactorMap['content-fields'][$typeIdentifier]['*'] = $factor;
  275. $boostFactorMap['meta-fields'][$typeIdentifier]['*'] = $factor;
  276. }
  277. foreach ($config['field_definition'] as $typeIdentifier => $mapping) {
  278. foreach ($mapping as $fieldIdentifier => $factor) {
  279. $boostFactorMap['content-fields'][$typeIdentifier][$fieldIdentifier] = $factor;
  280. }
  281. }
  282. foreach ($config['meta_field'] as $typeIdentifier => $mapping) {
  283. foreach ($mapping as $fieldIdentifier => $factor) {
  284. $boostFactorMap['meta-fields'][$typeIdentifier][$fieldIdentifier] = $factor;
  285. }
  286. }
  287. return $boostFactorMap;
  288. }
  289. /**
  290. * @phpstan-param SolrHttpClientConfigArray $httpClientConfig
  291. */
  292. private function configureHttpClient(ContainerBuilder $container, array $httpClientConfig): void
  293. {
  294. $container->setParameter('ibexa.solr.http_client.timeout', $httpClientConfig['timeout']);
  295. $container->setParameter(
  296. 'ibexa.solr.http_client.max_retries',
  297. $httpClientConfig['max_retries']
  298. );
  299. }
  300. }
  301. class_alias(IbexaSolrExtension::class, 'EzSystems\EzPlatformSolrSearchEngineBundle\DependencyInjection\EzSystemsEzPlatformSolrSearchEngineExtension');