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

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 $configsContainerBuilder $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.         foreach ($config['connections'] as $name => $params) {
  141.             $this->configureSearchServices($container$name$params);
  142.             $this->configureBoostMap($container$name$params);
  143.             $this->configureIndexingDepth($container$name$params);
  144.             $container->setParameter("$alias.connection.$name"$params);
  145.         }
  146.         foreach ($config['endpoints'] as $name => $params) {
  147.             $this->defineEndpoint($container$name$params);
  148.         }
  149.         // Search engine itself, for given connection name
  150.         $searchEngineDef $container->findDefinition(self::ENGINE_ID);
  151.         $searchEngineDef->setFactory([new Reference(SolrEngineFactory::class), 'buildEngine']);
  152.         // Factory for BoostFactorProvider uses mapping configured for the connection in use
  153.         $boostFactorProviderDef $container->findDefinition(self::BOOST_FACTOR_PROVIDER_ID);
  154.         $boostFactorProviderDef->setFactory([new Reference(BoostFactorProviderFactory::class), 'buildService']);
  155.         if (isset($config['http_client'])) {
  156.             $this->configureHttpClient($container$config['http_client']);
  157.         }
  158.     }
  159.     /**
  160.      * Creates needed search services for given connection name and parameters.
  161.      *
  162.      * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  163.      * @param string $connectionName
  164.      * @param array $connectionParams
  165.      */
  166.     private function configureSearchServices(ContainerBuilder $container$connectionName$connectionParams)
  167.     {
  168.         $alias $this->getServicePrefix();
  169.         // Endpoint resolver
  170.         $endpointResolverDefinition = new ChildDefinition(self::ENDPOINT_RESOLVER_ID);
  171.         $endpointResolverDefinition->replaceArgument(0$connectionParams['entry_endpoints']);
  172.         $endpointResolverDefinition->replaceArgument(1$connectionParams['mapping']['translations']);
  173.         $endpointResolverDefinition->replaceArgument(2$connectionParams['mapping']['default']);
  174.         $endpointResolverDefinition->replaceArgument(3$connectionParams['mapping']['main_translations']);
  175.         $endpointResolverId "$alias.connection.$connectionName.endpoint_resolver_id";
  176.         $container->setDefinition($endpointResolverId$endpointResolverDefinition);
  177.         // Core filter
  178.         $coreFilterDefinition = new ChildDefinition(self::CORE_FILTER_ID);
  179.         $coreFilterDefinition->replaceArgument(0, new Reference($endpointResolverId));
  180.         $coreFilterDefinition->addTag('ibexa.search.solr.core.filter', ['connection' => $connectionName]);
  181.         $coreFilterId "$alias.connection.$connectionName.core_filter_id";
  182.         $container->setDefinition($coreFilterId$coreFilterDefinition);
  183.         // Distribution Strategy
  184.         $distributionStrategyId "$alias.connection.$connectionName.distribution_strategy";
  185.         switch ($connectionParams['distribution_strategy']) {
  186.             case 'standalone':
  187.                 $distributionStrategyDefinition = new ChildDefinition(self::STANDALONE_DISTRIBUTION_STRATEGY_ID);
  188.                 $distributionStrategyDefinition->setArgument(1, new Reference($endpointResolverId));
  189.                 break;
  190.             case 'cloud':
  191.                 $distributionStrategyDefinition = new ChildDefinition(self::CLOUD_DISTRIBUTION_STRATEGY_ID);
  192.                 $distributionStrategyDefinition->setArgument(1, new Reference($endpointResolverId));
  193.                 break;
  194.             default:
  195.                 throw new \RuntimeException('Unknown distribution strategy');
  196.         }
  197.         $container->setDefinition($distributionStrategyId$distributionStrategyDefinition);
  198.         // Gateway
  199.         $gatewayDefinition = new ChildDefinition(self::GATEWAY_ID);
  200.         $gatewayDefinition->replaceArgument('$endpointResolver', new Reference($endpointResolverId));
  201.         $gatewayDefinition->replaceArgument('$distributionStrategy', new Reference($distributionStrategyId));
  202.         $gatewayDefinition->addTag('ibexa.search.solr.gateway', ['connection' => $connectionName]);
  203.         $gatewayId "$alias.connection.$connectionName.gateway_id";
  204.         $container->setDefinition($gatewayId$gatewayDefinition);
  205.     }
  206.     /**
  207.      * Creates boost factor map parameter for a given $connectionName.
  208.      *
  209.      * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  210.      * @param string $connectionName
  211.      * @param array $connectionParams
  212.      */
  213.     private function configureBoostMap(ContainerBuilder $container$connectionName$connectionParams)
  214.     {
  215.         $alias $this->getServicePrefix();
  216.         $boostFactorMap $this->buildBoostFactorMap($connectionParams['boost_factors']);
  217.         $boostFactorMapId "{$alias}.connection.{$connectionName}.boost_factor_map_id";
  218.         $container->setParameter($boostFactorMapId$boostFactorMap);
  219.     }
  220.     /**
  221.      * Creates indexing depth map parameter for a given $connectionName.
  222.      *
  223.      * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  224.      * @param string $connectionName
  225.      * @param array $connectionParams
  226.      */
  227.     private function configureIndexingDepth(ContainerBuilder $container$connectionName$connectionParams)
  228.     {
  229.         $alias $this->getServicePrefix();
  230.         $defaultIndexingDepthId "{$alias}.connection.{$connectionName}.indexing_depth.default";
  231.         $contentTypeIndexingDepthMapId "{$alias}.connection.{$connectionName}.indexing_depth.map";
  232.         $container->setParameter($defaultIndexingDepthId$connectionParams['indexing_depth']['default']);
  233.         $container->setParameter($contentTypeIndexingDepthMapId$connectionParams['indexing_depth']['content_type']);
  234.     }
  235.     /**
  236.      * Creates Endpoint definition in the service container.
  237.      *
  238.      * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
  239.      * @param string $alias
  240.      * @param array $params
  241.      */
  242.     protected function defineEndpoint(ContainerBuilder $container$alias$params)
  243.     {
  244.         $definition = new Definition(self::ENDPOINT_CLASS, [$params]);
  245.         $definition->addTag(self::ENDPOINT_TAG, ['alias' => $alias]);
  246.         $container->setDefinition(
  247.             sprintf($this->getServicePrefix() . '.endpoints.%s'$alias),
  248.             $definition
  249.         );
  250.     }
  251.     public function getConfiguration(array $configContainerBuilder $container)
  252.     {
  253.         return new Configuration($this->getAlias());
  254.     }
  255.     /**
  256.      * Builds boost factor map from the given $config.
  257.      *
  258.      * @see \Ibexa\Solr\FieldMapper\BoostFactorProvider::$map
  259.      *
  260.      * @param array $config
  261.      *
  262.      * @return array
  263.      */
  264.     protected function buildBoostFactorMap(array $config)
  265.     {
  266.         $boostFactorMap = [];
  267.         foreach ($config['content_type'] as $typeIdentifier => $factor) {
  268.             $boostFactorMap['content-fields'][$typeIdentifier]['*'] = $factor;
  269.             $boostFactorMap['meta-fields'][$typeIdentifier]['*'] = $factor;
  270.         }
  271.         foreach ($config['field_definition'] as $typeIdentifier => $mapping) {
  272.             foreach ($mapping as $fieldIdentifier => $factor) {
  273.                 $boostFactorMap['content-fields'][$typeIdentifier][$fieldIdentifier] = $factor;
  274.             }
  275.         }
  276.         foreach ($config['meta_field'] as $typeIdentifier => $mapping) {
  277.             foreach ($mapping as $fieldIdentifier => $factor) {
  278.                 $boostFactorMap['meta-fields'][$typeIdentifier][$fieldIdentifier] = $factor;
  279.             }
  280.         }
  281.         return $boostFactorMap;
  282.     }
  283.     /**
  284.      * @phpstan-param SolrHttpClientConfigArray $httpClientConfig
  285.      */
  286.     private function configureHttpClient(ContainerBuilder $container, array $httpClientConfig): void
  287.     {
  288.         $container->setParameter('ibexa.solr.http_client.timeout'$httpClientConfig['timeout']);
  289.         $container->setParameter(
  290.             'ibexa.solr.http_client.max_retries',
  291.             $httpClientConfig['max_retries']
  292.         );
  293.     }
  294. }
  295. class_alias(IbexaSolrExtension::class, 'EzSystems\EzPlatformSolrSearchEngineBundle\DependencyInjection\EzSystemsEzPlatformSolrSearchEngineExtension');