From 40a24514daa403e6fcc7ecc0f148ae30d12ad2bc Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Tue, 17 Jun 2025 15:16:06 +0200 Subject: [PATCH 1/3] Add support for configurable HTTP request factory for proxy clients (#646) --- CHANGELOG.md | 5 ++++ Resources/doc/features/invalidation.rst | 3 +- .../reference/configuration/proxy-client.rst | 9 ++++++ src/DependencyInjection/Configuration.php | 4 +++ .../FOSHttpCacheExtension.php | 30 +++++++++++++++++++ src/Resources/config/cloudflare.xml | 1 + src/Resources/config/fastly.xml | 1 + src/Resources/config/nginx.xml | 1 + src/Resources/config/symfony.xml | 1 + src/Resources/config/varnish.xml | 1 + .../DependencyInjection/ConfigurationTest.php | 7 +++++ 11 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc979cc..5959a1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Changelog 2.x === +2.18.0 +------ + +* New configuration option `proxy_client.*.http.request_factory` to support custom HTTP request factories for proxy clients. + 2.17.1 ------ diff --git a/Resources/doc/features/invalidation.rst b/Resources/doc/features/invalidation.rst index 0a68794c..53f29be9 100644 --- a/Resources/doc/features/invalidation.rst +++ b/Resources/doc/features/invalidation.rst @@ -66,7 +66,8 @@ To refresh paths and routes, you can use ``refreshPath($path, $headers)`` and If you want to add a header (such as ``Authorization``) to *all* invalidation requests, you can use a - :ref:`custom HTTP client ` instead. + :ref:`custom HTTP client ` or + :ref:`custom HTTP request factory ` instead. .. _invalidation configuration: diff --git a/Resources/doc/reference/configuration/proxy-client.rst b/Resources/doc/reference/configuration/proxy-client.rst index 07d73124..7359e72c 100644 --- a/Resources/doc/reference/configuration/proxy-client.rst +++ b/Resources/doc/reference/configuration/proxy-client.rst @@ -402,6 +402,15 @@ example to send a basic authentication header with each request, you can configure a service for the ``HttpClient`` and specify that in the ``http_client`` option of any of the cache proxy clients. +.. _custom HTTP request factory: + +Custom HTTP Request Factory +--------------------------- + +The proxy client uses an implementation of ``Http\Message\RequestFactory`` to create HTTP requests. +If you need to customize the request creation, you can configure your custom service and +specify that in the ``request_factory`` option of any of the cache proxy clients. + Caching Proxy Configuration --------------------------- diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 86afe546..007003a6 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -641,6 +641,10 @@ private function getHttpDispatcherNode() ->defaultNull() ->info('Httplug async client service name to use for sending the requests.') ->end() + ->scalarNode('request_factory') + ->defaultNull() + ->info('Service name of factory for PSR-7 messages.') + ->end() ->end() ; diff --git a/src/DependencyInjection/FOSHttpCacheExtension.php b/src/DependencyInjection/FOSHttpCacheExtension.php index c6d92b68..96236609 100644 --- a/src/DependencyInjection/FOSHttpCacheExtension.php +++ b/src/DependencyInjection/FOSHttpCacheExtension.php @@ -430,6 +430,12 @@ private function loadVarnish(ContainerBuilder $container, XmlFileLoader $loader, $container->setParameter('fos_http_cache.proxy_client.varnish.options', $options); $loader->load('varnish.xml'); + + $requestFactory = isset($config['http']['request_factory']) + ? new Reference($config['http']['request_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.varnish') + ->replaceArgument(2, $requestFactory); } private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, array $config) @@ -439,6 +445,12 @@ private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, a 'purge_location' => $config['purge_location'], ]); $loader->load('nginx.xml'); + + $requestFactory = isset($config['http']['request_factory']) + ? new Reference($config['http']['request_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.nginx') + ->replaceArgument(2, $requestFactory); } private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, array $config) @@ -465,6 +477,12 @@ private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, $container->setParameter('fos_http_cache.proxy_client.symfony.options', $options); $loader->load('symfony.xml'); + + $requestFactory = isset($config['http']['request_factory']) + ? new Reference($config['http']['request_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.symfony') + ->replaceArgument(2, $requestFactory); } private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $loader, array $config) @@ -478,6 +496,12 @@ private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $load $container->setParameter('fos_http_cache.proxy_client.cloudflare.options', $options); $loader->load('cloudflare.xml'); + + $requestFactory = isset($config['http']['request_factory']) + ? new Reference($config['http']['request_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.cloudflare') + ->replaceArgument(2, $requestFactory); } private function loadCloudfront(ContainerBuilder $container, XmlFileLoader $loader, array $config) @@ -514,6 +538,12 @@ private function loadFastly(ContainerBuilder $container, XmlFileLoader $loader, $container->setParameter('fos_http_cache.proxy_client.fastly.options', $options); $loader->load('fastly.xml'); + + $requestFactory = isset($config['http']['request_factory']) + ? new Reference($config['http']['request_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.fastly') + ->replaceArgument(2, $requestFactory); } /** diff --git a/src/Resources/config/cloudflare.xml b/src/Resources/config/cloudflare.xml index 3e38a0db..39ede98f 100644 --- a/src/Resources/config/cloudflare.xml +++ b/src/Resources/config/cloudflare.xml @@ -10,6 +10,7 @@ public="true"> %fos_http_cache.proxy_client.cloudflare.options% + diff --git a/src/Resources/config/fastly.xml b/src/Resources/config/fastly.xml index bf8475de..0f74344d 100755 --- a/src/Resources/config/fastly.xml +++ b/src/Resources/config/fastly.xml @@ -10,6 +10,7 @@ public="false"> %fos_http_cache.proxy_client.fastly.options% + diff --git a/src/Resources/config/nginx.xml b/src/Resources/config/nginx.xml index 7ec9f6fc..2553a782 100644 --- a/src/Resources/config/nginx.xml +++ b/src/Resources/config/nginx.xml @@ -10,6 +10,7 @@ public="true"> %fos_http_cache.proxy_client.nginx.options% + diff --git a/src/Resources/config/symfony.xml b/src/Resources/config/symfony.xml index 3e2a1411..0d07d586 100644 --- a/src/Resources/config/symfony.xml +++ b/src/Resources/config/symfony.xml @@ -10,6 +10,7 @@ public="true"> %fos_http_cache.proxy_client.symfony.options% + diff --git a/src/Resources/config/varnish.xml b/src/Resources/config/varnish.xml index 520d81f6..4a7c33c9 100644 --- a/src/Resources/config/varnish.xml +++ b/src/Resources/config/varnish.xml @@ -10,6 +10,7 @@ public="true"> %fos_http_cache.proxy_client.varnish.options% + diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index 50b40e3b..7d8f57b0 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -105,6 +105,7 @@ public function testSupportsAllConfigFormats() 'servers' => ['22.22.22.22'], 'base_url' => '/test', 'http_client' => 'acme.guzzle.varnish', + 'request_factory' => null, ], ], ], @@ -241,6 +242,7 @@ public function testSupportsNginx() 'servers' => ['22.22.22.22'], 'base_url' => '/test', 'http_client' => 'acme.guzzle.nginx', + 'request_factory' => null, ], ], ]; @@ -276,6 +278,7 @@ public function testSupportsSymfony() 'servers' => ['22.22.22.22'], 'base_url' => '/test', 'http_client' => 'acme.guzzle.symfony', + 'request_factory' => null, ], 'use_kernel_dispatcher' => false, ], @@ -505,6 +508,7 @@ public function testSplitOptions() 'base_url' => null, 'http_client' => null, 'servers' => ['1.1.1.1:80', '2.2.2.2:80'], + 'request_factory' => null, ], 'tags_header' => 'X-Cache-Tags', 'tag_mode' => 'ban', @@ -515,6 +519,7 @@ public function testSplitOptions() 'base_url' => null, 'http_client' => null, 'servers' => ['1.1.1.1:81', '2.2.2.2:81'], + 'request_factory' => null, ], ], ]; @@ -756,6 +761,7 @@ public function testUserContextLogoutHandler(string $configFile, $expected, $cac 'servers' => ['localhost'], 'base_url' => null, 'http_client' => null, + 'request_factory' => null, ]; $expectedConfiguration['proxy_client'][$proxyClient]['purge_location'] = false; } @@ -859,6 +865,7 @@ public function testSupportsServersFromJsonEnv(): void 'servers_from_jsonenv' => '%env(json:VARNISH_SERVERS)%', 'base_url' => '/test', 'http_client' => 'acme.guzzle.nginx', + 'request_factory' => null, ], 'tag_mode' => 'ban', 'tags_header' => 'X-Cache-Tags', From c1b8644677157807663c44459e1a718a3198f977 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 17 Jun 2025 15:33:27 +0200 Subject: [PATCH 2/3] improve doc for request_factory --- 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 bea6eb0b..b1dee3b2 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -626,7 +626,7 @@ private function getHttpDispatcherNode(): ArrayNodeDefinition ->end() ->scalarNode('request_factory') ->defaultNull() - ->info('Service name of factory for PSR-7 messages.') + ->info('Service name of PSR-17 factory for HTTP messages.') ->end() ->end() ; From f117fe9999031945e063a6b62a4f806e6f7c4438 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Tue, 17 Jun 2025 15:53:42 +0200 Subject: [PATCH 3/3] add support to configure stream factory as well --- CHANGELOG.md | 2 +- .../reference/configuration/proxy-client.rst | 18 +++++++---- composer.json | 4 +-- src/DependencyInjection/Configuration.php | 6 +++- .../FOSHttpCacheExtension.php | 30 +++++++++++++++++++ src/Resources/config/cloudflare.xml | 1 + src/Resources/config/fastly.xml | 1 + src/Resources/config/nginx.xml | 1 + src/Resources/config/symfony.xml | 1 + src/Resources/config/varnish.xml | 1 + .../DependencyInjection/ConfigurationTest.php | 8 +++++ 11 files changed, 64 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 744a06f7..e516b483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog * If a custom proxy client is configured on the cache manager, the `ProxyClient` class is an alias to that client, to support autowiring. * Attribute configuration now also works on single action controllers with the `__invoke` method. -* New configuration option `proxy_client.*.http.request_factory` to support custom HTTP request factories for proxy clients. +* New configuration option `proxy_client.*.http.request_factory` and `stream_factory` to support custom PSR-17 HTTP request and stream factories for proxy clients. 3.1.2 ----- diff --git a/Resources/doc/reference/configuration/proxy-client.rst b/Resources/doc/reference/configuration/proxy-client.rst index 76d1953d..40a3562b 100644 --- a/Resources/doc/reference/configuration/proxy-client.rst +++ b/Resources/doc/reference/configuration/proxy-client.rst @@ -402,12 +402,20 @@ configure a service for the ``HttpClient`` and specify that in the .. _custom HTTP request factory: -Custom HTTP Request Factory ---------------------------- +Custom HTTP Request Factory and Stream Factory +---------------------------------------------- + +The proxy client uses an implementation of PSR-17 ``Psr\Http\Message\RequestFactoryInterface`` +to create HTTP requests and ``Psr\Http\Message\StreamFactoryInterface`` to +create streams. + +If you need to customize request creation, you can configure your custom +service and specify that in the ``request_factory`` option of any of the cache +proxy clients. -The proxy client uses an implementation of ``Http\Message\RequestFactory`` to create HTTP requests. -If you need to customize the request creation, you can configure your custom service and -specify that in the ``request_factory`` option of any of the cache proxy clients. +If you need to customize stream creation, you can configure your custom service +and specify that in the ``stream_factory`` option of any of the cache proxy +clients. Caching Proxy Configuration --------------------------- diff --git a/composer.json b/composer.json index bb40fd6c..b69ddd0b 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ ], "require": { "php": "^8.1", - "friendsofsymfony/http-cache": "^2.15 || ^3.0", + "friendsofsymfony/http-cache": "^3.0", "symfony/dependency-injection": "^6.4 || ^7.0", "symfony/expression-language": "^6.4 || ^7.0", "symfony/framework-bundle": "^6.4 || ^7.0", @@ -60,7 +60,7 @@ "phpstan/phpstan": "^2", "phpstan/phpstan-symfony": "^2", "phpstan/extension-installer": "^1.4", - "jean-beru/fos-http-cache-cloudfront": "^1.1", + "jean-beru/fos-http-cache-cloudfront": "^1.1.1", "friendsofphp/php-cs-fixer": "^3.54" }, "suggest": { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index b1dee3b2..72fb70e9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -626,7 +626,11 @@ private function getHttpDispatcherNode(): ArrayNodeDefinition ->end() ->scalarNode('request_factory') ->defaultNull() - ->info('Service name of PSR-17 factory for HTTP messages.') + ->info('Service name of PSR-17 message factory.') + ->end() + ->scalarNode('stream_factory') + ->defaultNull() + ->info('Service name of PSR-17 stream factory.') ->end() ->end() ; diff --git a/src/DependencyInjection/FOSHttpCacheExtension.php b/src/DependencyInjection/FOSHttpCacheExtension.php index d9dff8f9..15e1aea3 100644 --- a/src/DependencyInjection/FOSHttpCacheExtension.php +++ b/src/DependencyInjection/FOSHttpCacheExtension.php @@ -433,6 +433,12 @@ private function loadVarnish(ContainerBuilder $container, XmlFileLoader $loader, : null; $container->getDefinition('fos_http_cache.proxy_client.varnish') ->replaceArgument(2, $requestFactory); + + $streamFactory = isset($config['http']['stream_factory']) + ? new Reference($config['http']['stream_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.varnish') + ->replaceArgument(3, $streamFactory); } private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, array $config): void @@ -448,6 +454,12 @@ private function loadNginx(ContainerBuilder $container, XmlFileLoader $loader, a : null; $container->getDefinition('fos_http_cache.proxy_client.nginx') ->replaceArgument(2, $requestFactory); + + $streamFactory = isset($config['http']['stream_factory']) + ? new Reference($config['http']['stream_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.nginx') + ->replaceArgument(3, $streamFactory); } private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, array $config): void @@ -480,6 +492,12 @@ private function loadSymfony(ContainerBuilder $container, XmlFileLoader $loader, : null; $container->getDefinition('fos_http_cache.proxy_client.symfony') ->replaceArgument(2, $requestFactory); + + $streamFactory = isset($config['http']['stream_factory']) + ? new Reference($config['http']['stream_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.symfony') + ->replaceArgument(3, $streamFactory); } private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $loader, array $config): void @@ -499,6 +517,12 @@ private function loadCloudflare(ContainerBuilder $container, XmlFileLoader $load : null; $container->getDefinition('fos_http_cache.proxy_client.cloudflare') ->replaceArgument(2, $requestFactory); + + $streamFactory = isset($config['http']['stream_factory']) + ? new Reference($config['http']['stream_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.cloudflare') + ->replaceArgument(3, $streamFactory); } private function loadCloudfront(ContainerBuilder $container, XmlFileLoader $loader, array $config): void @@ -541,6 +565,12 @@ private function loadFastly(ContainerBuilder $container, XmlFileLoader $loader, : null; $container->getDefinition('fos_http_cache.proxy_client.fastly') ->replaceArgument(2, $requestFactory); + + $streamFactory = isset($config['http']['stream_factory']) + ? new Reference($config['http']['stream_factory']) + : null; + $container->getDefinition('fos_http_cache.proxy_client.fastly') + ->replaceArgument(3, $streamFactory); } /** diff --git a/src/Resources/config/cloudflare.xml b/src/Resources/config/cloudflare.xml index 4d013ff4..5e92d5a6 100644 --- a/src/Resources/config/cloudflare.xml +++ b/src/Resources/config/cloudflare.xml @@ -12,6 +12,7 @@ %fos_http_cache.proxy_client.cloudflare.options% + diff --git a/src/Resources/config/fastly.xml b/src/Resources/config/fastly.xml index 8a00f3be..41277e2f 100755 --- a/src/Resources/config/fastly.xml +++ b/src/Resources/config/fastly.xml @@ -12,6 +12,7 @@ %fos_http_cache.proxy_client.fastly.options% + diff --git a/src/Resources/config/nginx.xml b/src/Resources/config/nginx.xml index 2553a782..f9421820 100644 --- a/src/Resources/config/nginx.xml +++ b/src/Resources/config/nginx.xml @@ -11,6 +11,7 @@ %fos_http_cache.proxy_client.nginx.options% + diff --git a/src/Resources/config/symfony.xml b/src/Resources/config/symfony.xml index 0d07d586..fcf2e334 100644 --- a/src/Resources/config/symfony.xml +++ b/src/Resources/config/symfony.xml @@ -11,6 +11,7 @@ %fos_http_cache.proxy_client.symfony.options% + diff --git a/src/Resources/config/varnish.xml b/src/Resources/config/varnish.xml index 4a7c33c9..57216740 100644 --- a/src/Resources/config/varnish.xml +++ b/src/Resources/config/varnish.xml @@ -11,6 +11,7 @@ %fos_http_cache.proxy_client.varnish.options% + diff --git a/tests/Unit/DependencyInjection/ConfigurationTest.php b/tests/Unit/DependencyInjection/ConfigurationTest.php index c5a29899..3a867380 100644 --- a/tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/tests/Unit/DependencyInjection/ConfigurationTest.php @@ -109,6 +109,7 @@ public function testSupportsAllConfigFormats(): void 'base_url' => '/test', 'http_client' => 'acme.guzzle.varnish', 'request_factory' => null, + 'stream_factory' => null, ], ], ], @@ -243,6 +244,7 @@ public function testSupportsNginx(): void 'base_url' => '/test', 'http_client' => 'acme.guzzle.nginx', 'request_factory' => null, + 'stream_factory' => null, ], ], ]; @@ -279,6 +281,7 @@ public function testSupportsSymfony(): void 'base_url' => '/test', 'http_client' => 'acme.guzzle.symfony', 'request_factory' => null, + 'stream_factory' => null, ], 'use_kernel_dispatcher' => false, ], @@ -510,6 +513,7 @@ public function testSplitOptions(): void 'http_client' => null, 'servers' => ['1.1.1.1:80', '2.2.2.2:80'], 'request_factory' => null, + 'stream_factory' => null, ], 'tags_header' => 'X-Cache-Tags', 'tag_mode' => 'ban', @@ -521,6 +525,7 @@ public function testSplitOptions(): void 'http_client' => null, 'servers' => ['1.1.1.1:81', '2.2.2.2:81'], 'request_factory' => null, + 'stream_factory' => null, ], ], ]; @@ -763,6 +768,7 @@ public function testUserContextLogoutHandler(string $configFile, $expected, $cac 'base_url' => null, 'http_client' => null, 'request_factory' => null, + 'stream_factory' => null, ]; $expectedConfiguration['proxy_client'][$proxyClient]['purge_location'] = false; } @@ -803,6 +809,7 @@ public function testSupportsServersFromJsonEnv(): void 'base_url' => '/test', 'http_client' => 'acme.guzzle.nginx', 'request_factory' => null, + 'stream_factory' => null, ], 'tag_mode' => 'ban', 'tags_header' => 'X-Cache-Tags', @@ -837,6 +844,7 @@ public function testConfigureExpressionLanguageService(): void 'base_url' => '/test', 'http_client' => 'acme.guzzle.nginx', 'request_factory' => null, + 'stream_factory' => null, ], 'tag_mode' => 'ban', 'tags_header' => 'X-Cache-Tags',