diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ad352160822ae..0430945be5fd1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1979,13 +1979,23 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->ifTrue(fn ($v) => !empty($v['query']) && !isset($v['base_uri'])) ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') ->end() + ->validate() + ->ifTrue(fn ($v) => ( + (isset($v['base_uri']) && \is_array($v['base_uri'])) + && ( + (!isset($v['retry_failed']) || false === $v['retry_failed']['enabled']) + || \count($v['base_uri']) !== \count(array_filter($v['base_uri'], 'is_string')) + ) + )) + ->thenInvalid('"base_uri" can only be an array if "retry_failed" is defined.') + ->end() ->children() ->scalarNode('scope') ->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.') ->cannotBeEmpty() ->end() - ->scalarNode('base_uri') - ->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.') + ->variableNode('base_uri') + ->info('The URI(s) to resolve relative URLs, following rules in RFC 3985, section 2.') ->cannotBeEmpty() ->end() ->scalarNode('auth_basic') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 039707722dee3..d529efc72d447 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2546,12 +2546,12 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder unset($scopeConfig['retry_failed']); if (null === $scope) { - $baseUri = $scopeConfig['base_uri']; - unset($scopeConfig['base_uri']); + $baseUri = \is_array($scopeConfig['base_uri']) ? $scopeConfig['base_uri'][0] : $scopeConfig['base_uri']; + $config = array_filter($scopeConfig, fn ($k) => 'base_uri' !== $k, \ARRAY_FILTER_USE_KEY); $container->register($name, ScopingHttpClient::class) ->setFactory([ScopingHttpClient::class, 'forBaseUri']) - ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig]) + ->setArguments([new Reference('http_client.transport'), $baseUri, $config]) ->addTag('http_client.client') ; } else { @@ -2562,7 +2562,12 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder } if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) { - $this->registerRetryableHttpClient($retryOptions, $name, $container); + $baseUris = []; + if (isset($scopeConfig['base_uri']) && \is_array($scopeConfig['base_uri'])) { + $baseUris = $scopeConfig['base_uri']; + unset($scopeConfig['base_uri']); + } + $this->registerRetryableHttpClient($retryOptions, $name, $container, $baseUris); } $container @@ -2598,7 +2603,7 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder } } - private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container, array $baseUris = []): void { if (null !== $options['retry_strategy']) { $retryStrategy = new Reference($options['retry_strategy']); @@ -2628,7 +2633,8 @@ private function registerRetryableHttpClient(array $options, string $name, Conta ->register($name.'.retryable', RetryableHttpClient::class) ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient (5) ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')]) - ->addTag('monolog.logger', ['channel' => 'http_client']); + ->addTag('monolog.logger', ['channel' => 'http_client']) + ->addMethodCall('withOptions', [['base_uri' => $baseUris]], true); } private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index aedd4a86fd113..7750db1f84637 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -668,6 +668,7 @@ + @@ -694,6 +695,7 @@ + @@ -747,6 +749,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_retry.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_retry.php index 28205f8e4ed8f..5ee718e762ce8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_retry.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_retry.php @@ -22,6 +22,10 @@ 'base_uri' => 'http://example.com', 'retry_failed' => ['multiplier' => 4], ], + 'bar' => [ + 'base_uri' => ['http://a.example.com', 'http://b.example.com'], + 'retry_failed' => ['max_retries' => 4], + ], ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_retry.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_retry.xml index 296d1b29cd7a6..889cdc040f722 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_retry.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_retry.xml @@ -26,6 +26,11 @@ + + http://a.example.com + http://a.example.com + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_retry.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_retry.yml index ea59b82d98e7a..dd98cbebb7007 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_retry.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_retry.yml @@ -21,3 +21,9 @@ framework: base_uri: http://example.com retry_failed: multiplier: 4 + bar: + base_uri: + - http://a.example.com + - http://b.example.com + retry_failed: + max_retries: 3 diff --git a/src/Symfony/Component/HttpClient/RetryableHttpClient.php b/src/Symfony/Component/HttpClient/RetryableHttpClient.php index d3b779420ffa9..c577a2d3ea3a4 100644 --- a/src/Symfony/Component/HttpClient/RetryableHttpClient.php +++ b/src/Symfony/Component/HttpClient/RetryableHttpClient.php @@ -39,12 +39,13 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface /** * @param int $maxRetries The maximum number of times to retry */ - public function __construct(HttpClientInterface $client, ?RetryStrategyInterface $strategy = null, int $maxRetries = 3, ?LoggerInterface $logger = null) + public function __construct(HttpClientInterface $client, ?RetryStrategyInterface $strategy = null, int $maxRetries = 3, ?LoggerInterface $logger = null, array $baseUris = []) { $this->client = $client; $this->strategy = $strategy ?? new GenericRetryStrategy(); $this->maxRetries = $maxRetries; $this->logger = $logger; + $this->baseUris = $baseUris; } public function withOptions(array $options): static