diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index df0d692ebdf7a..3355a0f52824c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -51,6 +51,7 @@ CHANGELOG 6.4 --- + * Add support for setting mock_response_factory per scoped http client * Add `HttpClientAssertionsTrait` * Add `AbstractController::renderBlock()` and `renderBlockView()` * Add native return type to `Translator` and to `Application::reset()` diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 92c20d139da6e..5011a7ecaaf67 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1866,8 +1866,13 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->append($this->createHttpClientRetrySection()) ->end() ->end() + ->booleanNode('mock_client') + ->info('Inject a mock client.') + ->defaultFalse() + ->end() ->scalarNode('mock_response_factory') - ->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable.') + ->defaultNull() + ->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable. Requires mock_client = true') ->end() ->arrayNode('scoped_clients') ->useAttributeAsKey('name') @@ -2006,6 +2011,12 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $e ->variableNode('md5')->end() ->end() ->end() + ->booleanNode('mock_client') + ->info('Inject a mock client.') + ->end() + ->scalarNode('mock_response_factory') + ->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable') + ->end() ->arrayNode('extra') ->info('Extra options for specific HTTP client') ->normalizeKeys(false) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 86355e5ca1e93..0a6f509aa5d14 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2456,6 +2456,14 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder ->getDefinition('http_client.uri_template') ->setArgument(2, $defaultUriTemplateVars); + $mockByDefault = $config['mock_client'] ?? false; + $defaultMockResponseFactory = $config['mock_response_factory'] ?? null; + if ($mockByDefault) { + $container->register('http_client.mock_client', MockHttpClient::class) + ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5) + ->setArguments($defaultMockResponseFactory === null ? [] : [new Reference($defaultMockResponseFactory)]); + } + foreach ($config['scoped_clients'] as $name => $scopeConfig) { if ($container->has($name)) { throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); @@ -2468,18 +2476,28 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false]; unset($scopeConfig['retry_failed']); + $httpClientTransport = new Reference('http_client.transport'); + + if ($scopeConfig['mock_client'] ?? $mockByDefault) { + $mockResponseFactory = $scopeConfig['mock_response_factory'] ?? $defaultMockResponseFactory; + $mockClientArguments = ($mockResponseFactory) ? [new Reference($mockResponseFactory)]: []; + $container->register('http_client.mock_client.'.$name, MockHttpClient::class) + ->setDecoratedService($httpClientTransport, null, -10) // lower priority than TraceableHttpClient (5) + ->setArguments($mockClientArguments); + } + if (null === $scope) { $baseUri = $scopeConfig['base_uri']; unset($scopeConfig['base_uri']); $container->register($name, ScopingHttpClient::class) ->setFactory([ScopingHttpClient::class, 'forBaseUri']) - ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig]) + ->setArguments([$httpClientTransport, $baseUri, $scopeConfig]) ->addTag('http_client.client') ; } else { $container->register($name, ScopingHttpClient::class) - ->setArguments([new Reference('http_client.transport'), [$scope => $scopeConfig], $scope]) + ->setArguments([$httpClientTransport, [$scope => $scopeConfig], $scope]) ->addTag('http_client.client') ; } @@ -2501,6 +2519,7 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $defaultUriTemplateVars, ]); + $container->registerAliasForArgument($name, HttpClientInterface::class); if ($hasPsr18) { @@ -2518,11 +2537,7 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder } } - if ($responseFactoryId = $config['mock_response_factory'] ?? null) { - $container->register('http_client.mock_client', MockHttpClient::class) - ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5) - ->setArguments([new Reference($responseFactoryId)]); - } + } private function registerThrottlingHttpClient(string $rateLimiter, string $name, ContainerBuilder $container): 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 d8d23168d1887..fe856463e1187 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 @@ -640,6 +640,7 @@ + @@ -697,6 +698,8 @@ + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index b32d8681b43b3..9ac71623c98e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -808,6 +808,8 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'disallow_search_engine_index' => true, 'http_client' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(HttpClient::class), + 'mock_client' => false, + 'mock_response_factory' => null, 'scoped_clients' => [], ], 'mailer' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock.php new file mode 100644 index 0000000000000..0496fd4e21bf0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock.php @@ -0,0 +1,26 @@ +dloadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'http_client' => [ + 'default_options' => null, + 'mock_client' => true, + 'scoped_clients' => [ + 'notMocked' => [ + 'base_uri' => 'https://symfony.com', + 'mock_client' => false, + ], + 'mocked' => [ + 'base_uri' => 'https://symfony.com' + ], + 'mocked_with_factory' => [ + 'base_uri' => 'https://symfony.com', + 'mock_response_factory' => 'my_response_factory' + ], + + ] + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock_response_factory.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock_response_factory.php index 326f0d25db503..57cc4290d2559 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock_response_factory.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_mock_response_factory.php @@ -7,6 +7,20 @@ 'php_errors' => ['log' => true], 'http_client' => [ 'default_options' => null, - 'mock_response_factory' => 'my_response_factory', + 'mock_client' => true, + 'mock_response_factory' => 'my_factory', + 'scoped_clients' => [ + 'notMocked' => [ + 'base_uri' => 'https://symfony.com', + 'mock_client' => false, + ], + 'mocked' => [ + 'base_uri' => 'https://symfony.com' + ], + 'mocked_custom_factory' => [ + 'base_uri' => 'https://symfony.com', + 'mock_response_factory' => 'my_other_factory' + ] + ] ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock.xml new file mode 100644 index 0000000000000..0dd3d74ee002f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock_response_factory.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock_response_factory.xml index d2bc058fb17ea..b99a4ed206f36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock_response_factory.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_mock_response_factory.xml @@ -8,8 +8,12 @@ - + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock.yml new file mode 100644 index 0000000000000..a6365674c3487 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock.yml @@ -0,0 +1,18 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + http_client: + default_options: ~ + mock_client: true + scoped_clients: + notMocked: + base_uri : https://symfony.com + mock_client : false + mocked: + base_uri: https://symfony.com + mocked_with_factory: + base_uri: https://symfony.com + mock_response_factory: 'my_response_factory' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock_response_factory.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock_response_factory.yml index 92c40b4591b1f..6283b80d26fc1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock_response_factory.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_mock_response_factory.yml @@ -6,4 +6,14 @@ framework: log: true http_client: default_options: ~ - mock_response_factory: my_response_factory + mock_client: true + mock_response_factory: 'my_factory' + scoped_clients: + notMocked: + base_uri : https://symfony.com + mock_client : false + mocked: + base_uri: https://symfony.com + mocked_custom_factory: + base_uri: https://symfony.com + mock_response_factory: 'my_other_factory' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory.yml new file mode 100644 index 0000000000000..c8c74e103be05 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory.yml @@ -0,0 +1,16 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + http_client: + default_options: ~ + mock_response_factory: ~ + + scoped_clients: + notMocked: + base_uri: https://symfony.com + mocked: + base_uri: https://symfony.com + mock_response_factory: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory_service.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory_service.yml new file mode 100644 index 0000000000000..b0a1500b0e5b5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_scoped_mock_response_factory_service.yml @@ -0,0 +1,16 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + http_client: + default_options: ~ + mock_response_factory: ~ + + scoped_clients: + notMocked: + base_uri: https://symfony.com + mocked: + base_uri: https://symfony.com + mock_response_factory: "my_response_factory" diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 2a26e786b436a..540be789d0bf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -2095,20 +2095,63 @@ public function testMailerWithSpecificMessageBus() $this->assertEquals(new Reference('app.another_bus'), $container->getDefinition('mailer.mailer')->getArgument(1)); } + public function testHttpClientNoMock() + { + $container = $this->createContainerFromFile('http_client_scoped_without_query_option'); + + $this->assertFalse($container->hasDefinition('http_client.mock_client.foo')); + $this->assertFalse($container->hasDefinition('http_client.mock_client')); + + } + public function testHttpClientMockResponseFactory() { $container = $this->createContainerFromFile('http_client_mock_response_factory'); - $definition = $container->getDefinition('http_client.mock_client'); + $this->assertFalse($container->hasDefinition('http_client.mock_client.notMocked')); + + $definition = $container->getDefinition('http_client.mock_client.mocked'); $this->assertSame(MockHttpClient::class, $definition->getClass()); $this->assertCount(1, $definition->getArguments()); $argument = $definition->getArgument(0); + $this->assertInstanceOf(Reference::class, $argument); + $this->assertSame('http_client.transport', current($definition->getDecoratedService())); + $this->assertSame('my_factory', (string) $argument); + + $definition = $container->getDefinition('http_client.mock_client.mocked_custom_factory'); + + $this->assertSame(MockHttpClient::class, $definition->getClass()); + $this->assertCount(1, $definition->getArguments()); + $argument = $definition->getArgument(0); $this->assertInstanceOf(Reference::class, $argument); $this->assertSame('http_client.transport', current($definition->getDecoratedService())); - $this->assertSame('my_response_factory', (string) $argument); + $this->assertSame('my_other_factory', (string) $argument); + } + + public function testHttpClientUseMockClientButOverrideInScopedClientsAndEnableFactories() + { + $container = $this->createContainerFromFile('http_client_mock'); + + $definition = $container->getDefinition('http_client.mock_client'); + + $this->assertSame(MockHttpClient::class, $definition->getClass()); + $this->assertCount(0, $definition->getArguments()); + + $this->assertFalse($container->hasDefinition('http_client.mock_client.notMocked')); + + $definition = $container->getDefinition('http_client.mock_client.mocked'); + + $this->assertSame(MockHttpClient::class, $definition->getClass()); + $this->assertCount(0, $definition->getArguments()); + + $definition = $container->getDefinition('http_client.mock_client.mocked_with_factory'); + + $this->assertSame(MockHttpClient::class, $definition->getClass()); + $this->assertCount(1, $definition->getArguments()); + } public function testRegisterParameterCollectingBehaviorDescribingTags()