From 118b11f9a14bc25e2fe3d745d91a1eb258e24126 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 28 Feb 2025 11:08:08 +0100 Subject: [PATCH 01/27] chore: dependency constraints (#6988) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 6b42bca..a49dc0c 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ ], "require": { "php": ">=8.2", - "api-platform/metadata": "^3.4 || ^4.0", - "api-platform/state": "^3.4 || ^4.0", + "api-platform/metadata": "^4.1", + "api-platform/state": "^4.1", "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { @@ -54,7 +54,7 @@ }, "extra": { "branch-alias": { - "dev-main": "4.0.x-dev", + "dev-main": "4.2.x-dev", "dev-3.4": "3.4.x-dev" }, "symfony": { From 70ae3fd9f7e0b5867ea9b936796a2aebc67bad09 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Thu, 13 Mar 2025 09:24:08 +0100 Subject: [PATCH 02/27] feat(httpcache): add more cache directives to AddHeadersProcessor (#7008) --- State/AddHeadersProcessor.php | 43 +++++++---------- Tests/State/AddHeadersProcessorTest.php | 62 ++++++++++++++++--------- 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/State/AddHeadersProcessor.php b/State/AddHeadersProcessor.php index c11c23d..8bab6f0 100644 --- a/State/AddHeadersProcessor.php +++ b/State/AddHeadersProcessor.php @@ -52,38 +52,31 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $resourceCacheHeaders = $operation->getCacheHeaders() ?? []; - if ($this->etag && !$response->getEtag()) { - $response->setEtag(hash('xxh3', (string) $content)); - } + $public = ($resourceCacheHeaders['public'] ?? $this->public); - if (null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age')) { - $response->setMaxAge($maxAge); - } + $options = [ + 'etag' => $this->etag && !$response->getEtag() ? hash('xxh3', (string) $content) : null, + 'max_age' => null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age') ? $maxAge : null, + // Cache-Control "s-maxage" is only relevant is resource is not marked as "private" + 's_maxage' => false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage') ? $sharedMaxAge : null, + 'public' => null !== $public && !$response->headers->hasCacheControlDirective('public') ? $public : null, + 'stale_while_revalidate' => null !== ($staleWhileRevalidate = $resourceCacheHeaders['stale_while_revalidate'] ?? $this->staleWhileRevalidate) && !$response->headers->hasCacheControlDirective('stale-while-revalidate') ? $staleWhileRevalidate : null, + 'stale_if_error' => null !== ($staleIfError = $resourceCacheHeaders['stale_if_error'] ?? $this->staleIfError) && !$response->headers->hasCacheControlDirective('stale-if-error') ? $staleIfError : null, + 'must_revalidate' => null !== ($mustRevalidate = $resourceCacheHeaders['must_revalidate'] ?? null) && !$response->headers->hasCacheControlDirective('must-revalidate') ? $mustRevalidate : null, + 'proxy_revalidate' => null !== ($proxyRevalidate = $resourceCacheHeaders['proxy_revalidate'] ?? null) && !$response->headers->hasCacheControlDirective('proxy-revalidate') ? $proxyRevalidate : null, + 'no_cache' => null !== ($noCache = $resourceCacheHeaders['no_cache'] ?? null) && !$response->headers->hasCacheControlDirective('no-cache') ? $noCache : null, + 'no_store' => null !== ($noStore = $resourceCacheHeaders['no_store'] ?? null) && !$response->headers->hasCacheControlDirective('no-store') ? $noStore : null, + 'no_transform' => null !== ($noTransform = $resourceCacheHeaders['no_transform'] ?? null) && !$response->headers->hasCacheControlDirective('no-transform') ? $noTransform : null, + 'immutable' => null !== ($immutable = $resourceCacheHeaders['immutable'] ?? null) && !$response->headers->hasCacheControlDirective('immutable') ? $immutable : null, + ]; + + $response->setCache($options); $vary = $resourceCacheHeaders['vary'] ?? $this->vary; if (null !== $vary) { $response->setVary(array_diff($vary, $response->getVary()), false); } - // if the public-property is defined and not yet set; apply it to the response - $public = ($resourceCacheHeaders['public'] ?? $this->public); - if (null !== $public && !$response->headers->hasCacheControlDirective('public')) { - $public ? $response->setPublic() : $response->setPrivate(); - } - - // Cache-Control "s-maxage" is only relevant is resource is not marked as "private" - if (false !== $public && null !== ($sharedMaxAge = $resourceCacheHeaders['shared_max_age'] ?? $this->sharedMaxAge) && !$response->headers->hasCacheControlDirective('s-maxage')) { - $response->setSharedMaxAge($sharedMaxAge); - } - - if (null !== ($staleWhileRevalidate = $resourceCacheHeaders['stale_while_revalidate'] ?? $this->staleWhileRevalidate) && !$response->headers->hasCacheControlDirective('stale-while-revalidate')) { - $response->headers->addCacheControlDirective('stale-while-revalidate', (string) $staleWhileRevalidate); - } - - if (null !== ($staleIfError = $resourceCacheHeaders['stale_if_error'] ?? $this->staleIfError) && !$response->headers->hasCacheControlDirective('stale-if-error')) { - $response->headers->addCacheControlDirective('stale-if-error', (string) $staleIfError); - } - return $response; } } diff --git a/Tests/State/AddHeadersProcessorTest.php b/Tests/State/AddHeadersProcessorTest.php index 384ac10..5472b5e 100644 --- a/Tests/State/AddHeadersProcessorTest.php +++ b/Tests/State/AddHeadersProcessorTest.php @@ -19,38 +19,54 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; class AddHeadersProcessorTest extends TestCase { - public function testAddHeaders(): void + public function testAddHeadersFromGlobalConfiguration(): void { $operation = new Get(); - $response = $this->createMock(Response::class); - $response->expects($this->once())->method('setEtag'); - $response->method('getContent')->willReturn('{}'); - $response->method('isSuccessful')->willReturn(true); - $response->headers = $this->createMock(ResponseHeaderBag::class); - $response->headers->method('hasCacheControlDirective')->with($this->logicalOr( - $this->identicalTo('public'), - $this->identicalTo('s-maxage'), - $this->identicalTo('max-age'), - $this->identicalTo('stale-while-revalidate'), - $this->identicalTo('stale-if-error'), - ))->willReturn(false); - $response->headers->expects($this->exactly(2))->method('addCacheControlDirective')->with($this->logicalOr( - $this->identicalTo('stale-while-revalidate'), - $this->identicalTo('stale-if-error'), - ), '10'); - $response->expects($this->once())->method('setPublic'); - $response->expects($this->once())->method('setMaxAge'); - $response->expects($this->once())->method('setSharedMaxAge'); - $request = $this->createMock(Request::class); - $request->method('isMethodCacheable')->willReturn(true); + $response = new Response('content'); + $request = new Request(); $context = ['request' => $request]; $decorated = $this->createMock(ProcessorInterface::class); $decorated->method('process')->willReturn($response); $processor = new AddHeadersProcessor($decorated, etag: true, maxAge: 100, sharedMaxAge: 200, vary: ['Accept', 'Accept-Encoding'], public: true, staleWhileRevalidate: 10, staleIfError: 10); + $processor->process($response, $operation, [], $context); + + self::assertSame('max-age=100, public, s-maxage=200, stale-if-error=10, stale-while-revalidate=10', $response->headers->get('cache-control')); + self::assertSame('"55f2b31a6acfaa64"', $response->headers->get('etag')); + self::assertSame(['Accept', 'Accept-Encoding'], $response->headers->all('vary')); + } + + public function testAddHeadersFromOperationConfiguration(): void + { + $operation = new Get( + cacheHeaders: [ + 'public' => false, + 'max_age' => 250, + 'shared_max_age' => 500, + 'stale_while_revalidate' => 30, + 'stale_if_error' => 15, + 'vary' => ['Authorization', 'Accept-Language'], + 'must_revalidate' => true, + 'proxy_revalidate' => true, + 'no_cache' => true, + 'no_store' => true, + 'no_transform' => true, + 'immutable' => true, + ], + ); + $response = new Response('content'); + $request = new Request(); + $context = ['request' => $request]; + $decorated = $this->createMock(ProcessorInterface::class); + $decorated->method('process')->willReturn($response); + $processor = new AddHeadersProcessor($decorated); + + $processor->process($response, $operation, [], $context); + + self::assertSame('immutable, max-age=250, must-revalidate, no-store, no-transform, private, proxy-revalidate, stale-if-error=15, stale-while-revalidate=30', $response->headers->get('cache-control')); + self::assertSame(['Authorization', 'Accept-Language'], $response->headers->all('vary')); } } From 2eff0c4ae6320426586db4ce5dcd730f51665ea5 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 11 Apr 2025 11:32:56 +0200 Subject: [PATCH 03/27] chore: phpunit missing deprecation triggers (#7059) --- phpunit.xml.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f4da180..ce04ce9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,6 +9,9 @@ + + trigger_deprecation + ./ From e561f4a2e0eb14539598407c1e69296fd5e7e24e Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 16 Apr 2025 21:29:35 +0200 Subject: [PATCH 04/27] feat: Use `Type` of `TypeInfo` instead of `PropertyInfo` (#6979) Co-authored-by: soyuka scopes: metadata, doctrine, json-schema --- composer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a49dc0c..77dbf68 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", + "symfony/type-info": "^7.3-dev", "phpunit/phpunit": "^11.2" }, "autoload": { @@ -67,5 +68,11 @@ }, "scripts": { "test": "./vendor/bin/phpunit" - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/symfony/type-info" + } + ] } From 053f7373c5bddec65a28135eba7e252659aaf8a1 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 18 Apr 2025 10:39:51 +0200 Subject: [PATCH 05/27] ci: patch phpunit deprecations inside component (#7103) --- composer.json | 10 ++++++++-- phpunit.xml.dist | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index a49dc0c..05c8a92 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "11.5.x-dev" }, "autoload": { "psr-4": { @@ -67,5 +67,11 @@ }, "scripts": { "test": "./vendor/bin/phpunit" - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/soyuka/phpunit" + } + ] } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ce04ce9..f9a00ac 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,7 @@ ./Tests/ - + trigger_deprecation From 54ece0a32050afa0636a56a16ef1a68dcd3f3b46 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 5 May 2025 13:26:52 +0200 Subject: [PATCH 06/27] chore: symfony/type-info 7.3.0-BETA1 --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 8162794..c907307 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", - "symfony/type-info": "^7.3-dev", + "symfony/type-info": "v7.3.0-BETA1", "phpunit/phpunit": "11.5.x-dev" }, "autoload": { @@ -70,10 +70,6 @@ "test": "./vendor/bin/phpunit" }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/symfony/type-info" - }, { "type": "vcs", "url": "https://github.com/soyuka/phpunit" From 280c9f3ad086a378f881c6d48c4eaa63d4899242 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 22 May 2025 15:13:30 +0200 Subject: [PATCH 07/27] chore: bump patch dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 05c8a92..2c35092 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ ], "require": { "php": ">=8.2", - "api-platform/metadata": "^4.1", - "api-platform/state": "^4.1", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { From 8005a4bbb80cdae767635e3f765b1af5280c6678 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 27 May 2025 15:41:14 +0200 Subject: [PATCH 08/27] chore: type-info v7.3.0-RC1 #7177 (#7178) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 45ddda5..790f8ee 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", - "symfony/type-info": "v7.3.0-BETA1", + "symfony/type-info": "v7.3.0-RC1", "phpunit/phpunit": "11.5.x-dev" }, "autoload": { From 2a30ad3d0e105bf86122c1de98ef3021de698384 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Wed, 28 May 2025 10:03:08 +0200 Subject: [PATCH 09/27] ci: prefer-lowest to avoid bumping inter components dependencies (#7169) --- composer.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2c35092..69e0e6d 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,8 @@ "extra": { "branch-alias": { "dev-main": "4.2.x-dev", - "dev-3.4": "3.4.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev" }, "symfony": { "require": "^6.4 || ^7.0" @@ -73,5 +74,6 @@ "type": "vcs", "url": "https://github.com/soyuka/phpunit" } - ] + ], + "version": "4.1.12" } From 9ebb2587704dd1f61aaccc323bf1bf3be51e29aa Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 2 Jun 2025 16:22:51 +0200 Subject: [PATCH 10/27] chore: use type-info:^7.3 (#7185) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 790f8ee..6c98e68 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0", - "symfony/type-info": "v7.3.0-RC1", + "symfony/type-info": "^7.3", "phpunit/phpunit": "11.5.x-dev" }, "autoload": { From 1d9bf1c393ae3e0f7b11f1f1ec30c04130084508 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 6 Jun 2025 16:02:15 +0200 Subject: [PATCH 11/27] fix: bump composer.json version nodes --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 69e0e6d..aab7af1 100644 --- a/composer.json +++ b/composer.json @@ -75,5 +75,5 @@ "url": "https://github.com/soyuka/phpunit" } ], - "version": "4.1.12" + "version": "4.1.14" } From 8e83d5905e8635f6abaf595c12a21194f1b2c4b9 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 6 Jun 2025 16:18:03 +0200 Subject: [PATCH 12/27] chore: missing "v" prefix in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aab7af1..2831db6 100644 --- a/composer.json +++ b/composer.json @@ -75,5 +75,5 @@ "url": "https://github.com/soyuka/phpunit" } ], - "version": "4.1.14" + "version": "v4.1.15" } From f65f092c90311a87ebb6dda87db3ca08b57c10d6 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 6 Jun 2025 16:56:47 +0200 Subject: [PATCH 13/27] ci: remove version from composer to avoid release side effects (#7196) --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2831db6..3cd2204 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,5 @@ "type": "vcs", "url": "https://github.com/soyuka/phpunit" } - ], - "version": "v4.1.15" + ] } From 88da524239457af5eed7e62a6f0ceb50ba17b06c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 1 Jul 2025 14:36:47 +0200 Subject: [PATCH 14/27] chore: more phpstan fixes (#7265) --- Tests/SouinPurgerTest.php | 87 ++++++++++----------------------- Tests/VarnishPurgerTest.php | 36 +++++--------- Tests/VarnishXKeyPurgerTest.php | 31 ++++-------- 3 files changed, 47 insertions(+), 107 deletions(-) diff --git a/Tests/SouinPurgerTest.php b/Tests/SouinPurgerTest.php index 1a07617..7bba11d 100644 --- a/Tests/SouinPurgerTest.php +++ b/Tests/SouinPurgerTest.php @@ -14,15 +14,13 @@ namespace ApiPlatform\HttpCache\Tests; use ApiPlatform\HttpCache\SouinPurger; -use GuzzleHttp\ClientInterface; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; /** * @author Sylvain Combraque @@ -59,35 +57,24 @@ private function generateXResourcesTags(int $number, int $minimum = 0): array public function testMultiChunkedTags(): void { - /** @var HttpClientInterface $client */ - $client = new class implements ClientInterface { + $client = new class implements HttpClientInterface { public array $sentRegexes = []; - public function send(RequestInterface $request, array $options = []): ResponseInterface - { - throw new \LogicException('Not implemented'); - } - - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { - throw new \LogicException('Not implemented'); - } - - public function request($method, $uri, array $options = []): ResponseInterface + public function request(string $method, string $url, array $options = []): ResponseInterface { $this->sentRegexes[] = $options['headers']['Surrogate-Key']; - return new Response(); + return new MockResponse(); } - public function requestAsync($method, $uri, array $options = []): PromiseInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { throw new \LogicException('Not implemented'); } - public function getConfig($option = null): void + public function withOptions(array $options): static { - throw new \LogicException('Not implemented'); + return $this; } }; $purger = new SouinPurger([$client]); @@ -96,71 +83,49 @@ public function getConfig($option = null): void self::assertSame([ implode(', ', $this->generateXResourcesTags(146)), implode(', ', $this->generateXResourcesTags(200, 146)), - ], $client->sentRegexes); // @phpstan-ignore-line + ], $client->sentRegexes); } public function testPurgeWithMultipleClients(): void { - /** @var HttpClientInterface $client1 */ - $client1 = new class implements ClientInterface { - public $requests = []; + $client1 = new class implements HttpClientInterface { + public array $requests = []; - public function send(RequestInterface $request, array $options = []): ResponseInterface - { - throw new \LogicException('Not implemented'); - } - - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { - throw new \LogicException('Not implemented'); - } - - public function request($method, $uri, array $options = []): ResponseInterface + public function request(string $method, string $url, array $options = []): ResponseInterface { $this->requests[] = [$method, 'http://dummy_host/dummy_api_path/souin_api', $options]; - return new Response(); + return new MockResponse(); } - public function requestAsync($method, $uri, array $options = []): PromiseInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { throw new \LogicException('Not implemented'); } - public function getConfig($option = null): void + public function withOptions(array $options): static { - throw new \LogicException('Not implemented'); + return $this; } }; - /** @var HttpClientInterface $client2 */ - $client2 = new class implements ClientInterface { - public $requests = []; + $client2 = new class implements HttpClientInterface { + public array $requests = []; - public function send(RequestInterface $request, array $options = []): ResponseInterface - { - throw new \LogicException('Not implemented'); - } - - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { - throw new \LogicException('Not implemented'); - } - - public function request($method, $uri, array $options = []): ResponseInterface + public function request(string $method, string $url, array $options = []): ResponseInterface { $this->requests[] = [$method, 'http://dummy_host/dummy_api_path/souin_api', $options]; - return new Response(); + return new MockResponse(); } - public function requestAsync($method, $uri, array $options = []): PromiseInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { throw new \LogicException('Not implemented'); } - public function getConfig($option = null): void + public function withOptions(array $options): static { - throw new \LogicException('Not implemented'); + return $this; } }; @@ -170,12 +135,12 @@ public function getConfig($option = null): void Request::METHOD_PURGE, 'http://dummy_host/dummy_api_path/souin_api', ['headers' => ['Surrogate-Key' => '/foo']], - ], $client1->requests[0]); // @phpstan-ignore-line + ], $client1->requests[0]); self::assertSame([ Request::METHOD_PURGE, 'http://dummy_host/dummy_api_path/souin_api', ['headers' => ['Surrogate-Key' => '/foo']], - ], $client2->requests[0]); // @phpstan-ignore-line + ], $client2->requests[0]); } public function testGetResponseHeaders(): void diff --git a/Tests/VarnishPurgerTest.php b/Tests/VarnishPurgerTest.php index 0cfb588..9577d81 100644 --- a/Tests/VarnishPurgerTest.php +++ b/Tests/VarnishPurgerTest.php @@ -14,15 +14,13 @@ namespace ApiPlatform\HttpCache\Tests; use ApiPlatform\HttpCache\VarnishPurger; -use GuzzleHttp\ClientInterface; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; /** * @author Kévin Dunglas @@ -59,10 +57,9 @@ public function testPurge(): void public function testEmptyTags(): void { - $clientProphecy1 = $this->prophesize(ClientInterface::class); + $clientProphecy1 = $this->prophesize(HttpClientInterface::class); $clientProphecy1->request()->shouldNotBeCalled(); - /** @var HttpClientInterface $client */ $client = $clientProphecy1->reveal(); $purger = new VarnishPurger([$client]); $purger->purge([]); @@ -71,42 +68,31 @@ public function testEmptyTags(): void #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $regexesToSend): void { - /** @var HttpClientInterface $client */ - $client = new class implements ClientInterface { + $client = new class implements HttpClientInterface { public array $sentRegexes = []; - public function send(RequestInterface $request, array $options = []): ResponseInterface - { - throw new \LogicException('Not implemented'); - } - - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { - throw new \LogicException('Not implemented'); - } - - public function request($method, $uri, array $options = []): ResponseInterface + public function request(string $method, string $url, array $options = []): ResponseInterface { $this->sentRegexes[] = $options['headers']['ApiPlatform-Ban-Regex']; - return new Response(); + return new MockResponse(); } - public function requestAsync($method, $uri, array $options = []): PromiseInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { throw new \LogicException('Not implemented'); } - public function getConfig($option = null): void + public function withOptions(array $options): static { - throw new \LogicException('Not implemented'); + return $this; } }; $purger = new VarnishPurger([$client], $maxHeaderLength); $purger->purge($iris); - self::assertSame($regexesToSend, $client->sentRegexes); // @phpstan-ignore-line + self::assertSame($regexesToSend, $client->sentRegexes); } public static function provideChunkHeaderCases(): \Generator diff --git a/Tests/VarnishXKeyPurgerTest.php b/Tests/VarnishXKeyPurgerTest.php index b73dc0e..bef8ad2 100644 --- a/Tests/VarnishXKeyPurgerTest.php +++ b/Tests/VarnishXKeyPurgerTest.php @@ -15,14 +15,14 @@ use ApiPlatform\HttpCache\VarnishXKeyPurger; use GuzzleHttp\ClientInterface; -use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\HttpClient\ResponseStreamInterface; /** * @author Kévin Dunglas @@ -101,42 +101,31 @@ public function testCustomGlue(): void #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $keysToSend): void { - /** @var HttpClientInterface $client */ - $client = new class implements ClientInterface { + $client = new class implements HttpClientInterface { public array $sentKeys = []; - public function send(RequestInterface $request, array $options = []): ResponseInterface - { - throw new \LogicException('Not implemented'); - } - - public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface - { - throw new \LogicException('Not implemented'); - } - - public function request($method, $uri, array $options = []): ResponseInterface + public function request(string $method, string $url, array $options = []): ResponseInterface { $this->sentKeys[] = $options['headers']['xkey']; - return new Response(); + return new MockResponse(); } - public function requestAsync($method, $uri, array $options = []): PromiseInterface + public function stream(ResponseInterface|iterable $responses, ?float $timeout = null): ResponseStreamInterface { throw new \LogicException('Not implemented'); } - public function getConfig($option = null): void + public function withOptions(array $options): static { - throw new \LogicException('Not implemented'); + return $this; } }; $purger = new VarnishXKeyPurger([$client], $maxHeaderLength); $purger->purge($iris); - self::assertSame($keysToSend, $client->sentKeys); // @phpstan-ignore-line + self::assertSame($keysToSend, $client->sentKeys); } public static function provideChunkHeaderCases(): \Generator From 046f0be22d1e7e767d3603c03e23eab28a1bef0d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 25 Jul 2025 11:37:01 +0200 Subject: [PATCH 15/27] feat(metadata): class is now class-string (#7307) --- Tests/State/AddTagsProcessorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/State/AddTagsProcessorTest.php b/Tests/State/AddTagsProcessorTest.php index 4413bda..210258e 100644 --- a/Tests/State/AddTagsProcessorTest.php +++ b/Tests/State/AddTagsProcessorTest.php @@ -51,7 +51,7 @@ public function testAddTags(): void public function testAddTagsCollection(): void { - $operation = new GetCollection(class: 'Foo', uriVariables: ['id' => new Link()]); + $operation = new GetCollection(class: \stdClass::class, uriVariables: ['id' => new Link()]); $response = $this->createMock(Response::class); $response->method('isCacheable')->willReturn(true); $response->headers = $this->createMock(ResponseHeaderBag::class); @@ -65,7 +65,7 @@ public function testAddTagsCollection(): void $decorated = $this->createMock(ProcessorInterface::class); $decorated->method('process')->willReturn($response); $iriConverter = $this->createMock(IriConverterInterface::class); - $iriConverter->expects($this->once())->method('getIriFromResource')->with('Foo', UrlGeneratorInterface::ABS_PATH, $operation, ['uri_variables' => ['id' => 1]])->willReturn('/foos/1/bars'); + $iriConverter->expects($this->once())->method('getIriFromResource')->with(\stdClass::class, UrlGeneratorInterface::ABS_PATH, $operation, ['uri_variables' => ['id' => 1]])->willReturn('/foos/1/bars'); $processor = new AddTagsProcessor($decorated, $iriConverter); $processor->process($response, $operation, [], $context); } From 31d4a50401d2e3f6c29952c55b38f9fb81b3f545 Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 19 Aug 2025 10:04:29 +0200 Subject: [PATCH 16/27] chore: 4.2 branch alias --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7047155..533e572 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,8 @@ }, "extra": { "branch-alias": { - "dev-main": "4.2.x-dev", + "dev-main": "4.3.x-dev", + "dev-4.2": "4.2.x-dev", "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev" }, From 3a6b69e57280b30d8db5a25bf3f52edb7f9069fc Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 16 Sep 2025 09:16:14 +0200 Subject: [PATCH 17/27] fix(laravel): http cache compatibility (#7380) --- SouinPurger.php | 4 ++-- State/AddHeadersProcessor.php | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/SouinPurger.php b/SouinPurger.php index 70b3727..4fc2ff7 100644 --- a/SouinPurger.php +++ b/SouinPurger.php @@ -29,8 +29,8 @@ class SouinPurger extends SurrogateKeysPurger /** * @param HttpClientInterface[] $clients */ - public function __construct(iterable $clients) + public function __construct(iterable $clients, int $maxHeaderLength = self::MAX_HEADER_SIZE_PER_BATCH) { - parent::__construct($clients, self::MAX_HEADER_SIZE_PER_BATCH, self::HEADER, self::SEPARATOR); + parent::__construct($clients, $maxHeaderLength, self::HEADER, self::SEPARATOR); } } diff --git a/State/AddHeadersProcessor.php b/State/AddHeadersProcessor.php index c11c23d..a19d88f 100644 --- a/State/AddHeadersProcessor.php +++ b/State/AddHeadersProcessor.php @@ -29,8 +29,16 @@ final class AddHeadersProcessor implements ProcessorInterface /** * @param ProcessorInterface $decorated */ - public function __construct(private readonly ProcessorInterface $decorated, private readonly bool $etag = false, private readonly ?int $maxAge = null, private readonly ?int $sharedMaxAge = null, private readonly ?array $vary = null, private readonly ?bool $public = null, private readonly ?int $staleWhileRevalidate = null, private readonly ?int $staleIfError = null) - { + public function __construct( + private readonly ProcessorInterface $decorated, + private readonly bool $etag = false, + private readonly ?int $maxAge = null, + private readonly ?int $sharedMaxAge = null, + private readonly ?array $vary = null, + private readonly ?bool $public = null, + private readonly ?int $staleWhileRevalidate = null, + private readonly ?int $staleIfError = null, + ) { } public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed From 3d0a384273c3771b3f299be35903cb53fea3169c Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 31 Oct 2025 17:12:05 +0100 Subject: [PATCH 18/27] chore: bump deps --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 533e572..d6ab71a 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ ], "require": { "php": ">=8.2", - "api-platform/metadata": "^4.1.11", - "api-platform/state": "^4.1.11", + "api-platform/metadata": "^4.2", + "api-platform/state": "^4.2", "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { From 56d807edefa8818211052f76bf1653d03f85b431 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 13 Nov 2025 15:23:22 +0100 Subject: [PATCH 19/27] chore: symfony/http-foundation:^6.4.14 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d6ab71a..1ad8bc0 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "php": ">=8.2", "api-platform/metadata": "^4.2", "api-platform/state": "^4.2", - "symfony/http-foundation": "^6.4 || ^7.0" + "symfony/http-foundation": "^6.4.14 || ^7.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.0 || ^7.0", From cdc153e8c9bd12b20cb5366cd6c4ccd335a9cf60 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 13 Nov 2025 16:19:51 +0100 Subject: [PATCH 20/27] chore: bump self dependencies to avoid installing lowest --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1ad8bc0..7194c28 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "require": { "php": ">=8.2", "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2", + "api-platform/state": "^4.2.4", "symfony/http-foundation": "^6.4.14 || ^7.0" }, "require-dev": { From 0af8087a824338bbf60f942c9609576cd159663c Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 13 Nov 2025 16:57:14 +0100 Subject: [PATCH 21/27] chore: http-foundation:^6.4.14 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3cd2204..c60c5fa 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "php": ">=8.2", "api-platform/metadata": "^4.1.11", "api-platform/state": "^4.1.11", - "symfony/http-foundation": "^6.4 || ^7.0" + "symfony/http-foundation": "^6.4.14 || ^7.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.0 || ^7.0", From 4bb2eab81407f493f54f51be7aa1918f362c14b5 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Sun, 30 Nov 2025 13:55:42 +0100 Subject: [PATCH 22/27] chore: support symfony 8 (#7561) --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 7194c28..9ab7341 100644 --- a/composer.json +++ b/composer.json @@ -25,14 +25,14 @@ "php": ">=8.2", "api-platform/metadata": "^4.2", "api-platform/state": "^4.2.4", - "symfony/http-foundation": "^6.4.14 || ^7.0" + "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.0 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^6.4 || ^7.0 || ^8.0", "phpspec/prophecy-phpunit": "^2.2", - "symfony/http-client": "^6.4 || ^7.0", - "symfony/type-info": "^7.3", + "symfony/http-client": "^6.4 || ^7.0 || ^8.0", + "symfony/type-info": "^7.3 || ^8.0", "phpunit/phpunit": "11.5.x-dev" }, "autoload": { @@ -61,7 +61,7 @@ "dev-4.1": "4.1.x-dev" }, "symfony": { - "require": "^6.4 || ^7.0" + "require": "^6.4 || ^7.0 || ^8.0" }, "thanks": { "name": "api-platform/api-platform", From 7679a23ce4bf8f35a69d94ac060f6d13ab88497b Mon Sep 17 00:00:00 2001 From: soyuka Date: Sat, 27 Dec 2025 22:56:46 +0100 Subject: [PATCH 23/27] ci: upgrade to phpunit 12 Remove soyuka/phpunit fork from all composer.json files and upgrade to PHPUnit 12.2. Update CI workflow to install PHPUnit before other steps and configure MongoDB conditional execution. Migrate tests from Prophecy to PHPUnit native mocking in FieldsBuilderTest and Symfony event listener tests. Remove unused dataprovider and fix warnings. --- composer.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 9ab7341..7f8ba10 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "phpspec/prophecy-phpunit": "^2.2", "symfony/http-client": "^6.4 || ^7.0 || ^8.0", "symfony/type-info": "^7.3 || ^8.0", - "phpunit/phpunit": "11.5.x-dev" + "phpunit/phpunit": "^12.2" }, "autoload": { "psr-4": { @@ -70,11 +70,5 @@ }, "scripts": { "test": "./vendor/bin/phpunit" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/soyuka/phpunit" - } - ] + } } From 04a9239b67425f68ed2d372c2c731f14342dea45 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 12 Jan 2026 14:36:15 +0100 Subject: [PATCH 24/27] chore: phpdoc for array of some interface (#7583) --- PurgerInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PurgerInterface.php b/PurgerInterface.php index 6b55712..b47127b 100644 --- a/PurgerInterface.php +++ b/PurgerInterface.php @@ -31,6 +31,8 @@ public function purge(array $iris): void; * Get the response header containing purged tags. * * @param string[] $iris + * + * @return array */ public function getResponseHeaders(array $iris): array; } From ec5f9068d3d66be63db4d80acaf518868dea1321 Mon Sep 17 00:00:00 2001 From: Maxcastel <92802347+Maxcastel@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:07:33 +0100 Subject: [PATCH 25/27] refactor: use imported DataProvider instead of FQCN (#7753) Co-authored-by: Antoine Bluchet --- Tests/VarnishPurgerTest.php | 3 ++- Tests/VarnishXKeyPurgerTest.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/VarnishPurgerTest.php b/Tests/VarnishPurgerTest.php index 9577d81..4352067 100644 --- a/Tests/VarnishPurgerTest.php +++ b/Tests/VarnishPurgerTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\HttpCache\Tests; use ApiPlatform\HttpCache\VarnishPurger; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; @@ -65,7 +66,7 @@ public function testEmptyTags(): void $purger->purge([]); } - #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] + #[DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $regexesToSend): void { $client = new class implements HttpClientInterface { diff --git a/Tests/VarnishXKeyPurgerTest.php b/Tests/VarnishXKeyPurgerTest.php index bef8ad2..2340f22 100644 --- a/Tests/VarnishXKeyPurgerTest.php +++ b/Tests/VarnishXKeyPurgerTest.php @@ -16,6 +16,7 @@ use ApiPlatform\HttpCache\VarnishXKeyPurger; use GuzzleHttp\ClientInterface; use GuzzleHttp\Psr7\Response; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; @@ -98,7 +99,7 @@ public function testCustomGlue(): void $purger->purge(['/foo', '/bar', '/baz']); } - #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] + #[DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $keysToSend): void { $client = new class implements HttpClientInterface { From 7c15b4ca15067a013ff0a7c5682527a1431d6e61 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 6 Mar 2026 10:30:10 +0100 Subject: [PATCH 26/27] chore: bump sub-components dependencies to ^4.3 (#7820) --- composer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 7f8ba10..53e1416 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ ], "require": { "php": ">=8.2", - "api-platform/metadata": "^4.2", - "api-platform/state": "^4.2.4", + "api-platform/metadata": "^4.3", + "api-platform/state": "^4.3", "symfony/http-foundation": "^6.4.14 || ^7.0 || ^8.0" }, "require-dev": { @@ -70,5 +70,7 @@ }, "scripts": { "test": "./vendor/bin/phpunit" - } + }, + "minimum-stability": "beta", + "prefer-stable": true } From 9ffbe58f8872932ed7c2afca8207c2d68629e037 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 6 Mar 2026 16:07:49 +0100 Subject: [PATCH 27/27] chore: switch dev-main branch alias to 4.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 53e1416..f816b08 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ }, "extra": { "branch-alias": { - "dev-main": "4.3.x-dev", + "dev-main": "4.4.x-dev", "dev-4.2": "4.2.x-dev", "dev-3.4": "3.4.x-dev", "dev-4.1": "4.1.x-dev"