From 947fb1dbc4b1eabefae8134cb562c15ae182caf2 Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 25 Jun 2024 22:24:35 +0200 Subject: [PATCH 01/40] chore: remove deprecations --- EventListener/AddHeadersListener.php | 93 --------------------------- EventListener/AddTagsListener.php | 95 ---------------------------- Tests/VarnishPurgerTest.php | 4 +- Tests/VarnishXKeyPurgerTest.php | 4 +- composer.json | 5 +- phpunit.xml.dist | 47 ++++++-------- 6 files changed, 22 insertions(+), 226 deletions(-) delete mode 100644 EventListener/AddHeadersListener.php delete mode 100644 EventListener/AddTagsListener.php diff --git a/EventListener/AddHeadersListener.php b/EventListener/AddHeadersListener.php deleted file mode 100644 index 516ce45..0000000 --- a/EventListener/AddHeadersListener.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\HttpCache\EventListener; - -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\State\Util\OperationRequestInitiatorTrait; -use ApiPlatform\State\Util\RequestAttributesExtractor; -use Symfony\Component\HttpKernel\Event\ResponseEvent; - -/** - * Configures cache HTTP headers for the current response. - * - * @author Kévin Dunglas - * - * @deprecated use \Symfony\EventListener\AddHeadersListener.php instead - */ -final class AddHeadersListener -{ - use OperationRequestInitiatorTrait; - - public function __construct(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, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, private readonly ?int $staleWhileRevalidate = null, private readonly ?int $staleIfError = null) - { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - } - - public function onKernelResponse(ResponseEvent $event): void - { - $request = $event->getRequest(); - if (!$request->isMethodCacheable()) { - return; - } - - $attributes = RequestAttributesExtractor::extractAttributes($request); - if (\count($attributes) < 1) { - return; - } - - $response = $event->getResponse(); - - if (!$response->getContent() || !$response->isSuccessful()) { - return; - } - - $operation = $this->initializeOperation($request); - if ('api_platform.symfony.main_controller' === $operation?->getController()) { - return; - } - $resourceCacheHeaders = $attributes['cache_headers'] ?? $operation?->getCacheHeaders() ?? []; - - if ($this->etag && !$response->getEtag()) { - $response->setEtag(md5((string) $response->getContent())); - } - - if (null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age')) { - $response->setMaxAge($maxAge); - } - - $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); - } - } -} diff --git a/EventListener/AddTagsListener.php b/EventListener/AddTagsListener.php deleted file mode 100644 index 74cd37e..0000000 --- a/EventListener/AddTagsListener.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\HttpCache\EventListener; - -use ApiPlatform\HttpCache\PurgerInterface; -use ApiPlatform\Metadata\CollectionOperationInterface; -use ApiPlatform\Metadata\IriConverterInterface; -use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use ApiPlatform\Metadata\UrlGeneratorInterface; -use ApiPlatform\State\UriVariablesResolverTrait; -use ApiPlatform\State\Util\OperationRequestInitiatorTrait; -use ApiPlatform\State\Util\RequestAttributesExtractor; -use Symfony\Component\HttpKernel\Event\ResponseEvent; - -/** - * Sets the list of resources' IRIs included in this response in the configured cache tag HTTP header and/or "xkey" HTTP headers. - * - * By default the "Cache-Tags" HTTP header is used because it is supported by CloudFlare. - * - * @see https://developers.cloudflare.com/cache/how-to/purge-cache#add-cache-tag-http-response-headers - * - * The "xkey" is used because it is supported by Varnish. - * @see https://docs.varnish-software.com/varnish-cache-plus/vmods/ykey/ - * - * @author Kévin Dunglas - * - * @deprecated use \Symfony\EventListener\AddTagsListener.php instead - */ -final class AddTagsListener -{ - use OperationRequestInitiatorTrait; - use UriVariablesResolverTrait; - - public function __construct(private readonly IriConverterInterface $iriConverter, ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, private readonly ?PurgerInterface $purger = null) - { - $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; - } - - /** - * Adds the configured HTTP cache tag and "xkey" headers. - */ - public function onKernelResponse(ResponseEvent $event): void - { - $request = $event->getRequest(); - $operation = $this->initializeOperation($request); - if ('api_platform.symfony.main_controller' === $operation?->getController()) { - return; - } - $response = $event->getResponse(); - - if ( - !$request->isMethodCacheable() - || !$response->isCacheable() - || (!$attributes = RequestAttributesExtractor::extractAttributes($request)) - ) { - return; - } - - $resources = $request->attributes->get('_resources'); - if ($operation instanceof CollectionOperationInterface) { - // Allows to purge collections - $uriVariables = $this->getOperationUriVariables($operation, $request->attributes->all(), $attributes['resource_class']); - $iri = $this->iriConverter->getIriFromResource($attributes['resource_class'], UrlGeneratorInterface::ABS_PATH, $operation, ['uri_variables' => $uriVariables]); - - $resources[$iri] = $iri; - } - - if (!$resources) { - return; - } - - if (!$this->purger) { - $response->headers->set('Cache-Tags', implode(',', $resources)); - - return; - } - - $headers = $this->purger->getResponseHeaders($resources); - - foreach ($headers as $key => $value) { - $response->headers->set($key, $value); - } - } -} diff --git a/Tests/VarnishPurgerTest.php b/Tests/VarnishPurgerTest.php index 1217c69..65c336e 100644 --- a/Tests/VarnishPurgerTest.php +++ b/Tests/VarnishPurgerTest.php @@ -68,9 +68,7 @@ public function testEmptyTags(): void $purger->purge([]); } - /** - * @dataProvider provideChunkHeaderCases - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $regexesToSend): void { /** @var HttpClientInterface $client */ diff --git a/Tests/VarnishXKeyPurgerTest.php b/Tests/VarnishXKeyPurgerTest.php index 85e8316..0e029f1 100644 --- a/Tests/VarnishXKeyPurgerTest.php +++ b/Tests/VarnishXKeyPurgerTest.php @@ -98,9 +98,7 @@ public function testCustomGlue(): void $purger->purge(['/foo', '/bar', '/baz']); } - /** - * @dataProvider provideChunkHeaderCases - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideChunkHeaderCases')] public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $keysToSend): void { /** @var HttpClientInterface $client */ diff --git a/composer.json b/composer.json index d37dd56..f775b48 100644 --- a/composer.json +++ b/composer.json @@ -30,8 +30,7 @@ "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.0", "symfony/phpunit-bridge": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0", - "sebastian/comparator": "<5.0" + "symfony/http-client": "^6.4 || ^7.0" }, "autoload": { "psr-4": { @@ -63,4 +62,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} \ No newline at end of file +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d2a2213..f4da180 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,31 +1,20 @@ - - - - - - - - - ./Tests/ - - - - - - ./ - - - ./Tests - ./vendor - - + + + + + + + ./Tests/ + + + + + ./ + + + ./Tests + ./vendor + + - From e3feecf2cafa42b820173aa0df19552bf3281d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 21 Jul 2024 11:36:32 +0200 Subject: [PATCH 02/40] perf: use XXH3 instead of MD5 (#6487) * perf: use XXH3 instead of MD5 * fix Behat test --- State/AddHeadersProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/State/AddHeadersProcessor.php b/State/AddHeadersProcessor.php index e1c008b..c11c23d 100644 --- a/State/AddHeadersProcessor.php +++ b/State/AddHeadersProcessor.php @@ -53,7 +53,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $resourceCacheHeaders = $operation->getCacheHeaders() ?? []; if ($this->etag && !$response->getEtag()) { - $response->setEtag(md5((string) $content)); + $response->setEtag(hash('xxh3', (string) $content)); } if (null !== ($maxAge = $resourceCacheHeaders['max_age'] ?? $this->maxAge) && !$response->headers->hasCacheControlDirective('max-age')) { From a0c7b0cb8662078a9458296a22bf56e7abd2cf2e Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 9 Aug 2024 09:49:50 +0200 Subject: [PATCH 03/40] style: cs fixes --- SurrogateKeysPurger.php | 4 ++-- Tests/SouinPurgerTest.php | 8 ++++---- Tests/VarnishPurgerTest.php | 6 +++--- Tests/VarnishXKeyPurgerTest.php | 2 +- VarnishPurger.php | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SurrogateKeysPurger.php b/SurrogateKeysPurger.php index 0206dea..0890d5e 100644 --- a/SurrogateKeysPurger.php +++ b/SurrogateKeysPurger.php @@ -46,7 +46,7 @@ private function getChunkedIris(array $iris): \Iterator $chunk = array_shift($iris); foreach ($iris as $iri) { - $nextChunk = sprintf('%s%s%s', $chunk, $this->separator, $iri); + $nextChunk = \sprintf('%s%s%s', $chunk, $this->separator, $iri); if (\strlen($nextChunk) <= $this->maxHeaderLength) { $chunk = $nextChunk; continue; @@ -66,7 +66,7 @@ public function purge(array $iris): void { foreach ($this->getChunkedIris($iris) as $chunk) { if (\strlen((string) $chunk) > $this->maxHeaderLength) { - throw new RuntimeException(sprintf('IRI "%s" is too long to fit current max header length (currently set to "%s"). You can increase it using the "api_platform.http_cache.invalidation.max_header_length" parameter.', $chunk, $this->maxHeaderLength)); + throw new RuntimeException(\sprintf('IRI "%s" is too long to fit current max header length (currently set to "%s"). You can increase it using the "api_platform.http_cache.invalidation.max_header_length" parameter.', $chunk, $this->maxHeaderLength)); } foreach ($this->clients as $client) { diff --git a/Tests/SouinPurgerTest.php b/Tests/SouinPurgerTest.php index ac66be6..1a07617 100644 --- a/Tests/SouinPurgerTest.php +++ b/Tests/SouinPurgerTest.php @@ -51,7 +51,7 @@ private function generateXResourcesTags(int $number, int $minimum = 0): array $stack = []; for ($i = $minimum; $i < $number; ++$i) { - $stack[] = sprintf('/tags/%d', $i); + $stack[] = \sprintf('/tags/%d', $i); } return $stack; @@ -60,7 +60,7 @@ 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 ClientInterface { public array $sentRegexes = []; public function send(RequestInterface $request, array $options = []): ResponseInterface @@ -102,7 +102,7 @@ public function getConfig($option = null): void public function testPurgeWithMultipleClients(): void { /** @var HttpClientInterface $client1 */ - $client1 = new class() implements ClientInterface { + $client1 = new class implements ClientInterface { public $requests = []; public function send(RequestInterface $request, array $options = []): ResponseInterface @@ -133,7 +133,7 @@ public function getConfig($option = null): void } }; /** @var HttpClientInterface $client2 */ - $client2 = new class() implements ClientInterface { + $client2 = new class implements ClientInterface { public $requests = []; public function send(RequestInterface $request, array $options = []): ResponseInterface diff --git a/Tests/VarnishPurgerTest.php b/Tests/VarnishPurgerTest.php index 65c336e..0cfb588 100644 --- a/Tests/VarnishPurgerTest.php +++ b/Tests/VarnishPurgerTest.php @@ -72,7 +72,7 @@ public function testEmptyTags(): void public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $regexesToSend): void { /** @var HttpClientInterface $client */ - $client = new class() implements ClientInterface { + $client = new class implements ClientInterface { public array $sentRegexes = []; public function send(RequestInterface $request, array $options = []): ResponseInterface @@ -169,8 +169,8 @@ public static function provideChunkHeaderCases(): \Generator 8000, array_fill(0, 3000, '/foo'), [ - sprintf('(%s)($|\,)', implode('|', array_fill(0, 1598, '/foo'))), - sprintf('(%s)($|\,)', implode('|', array_fill(0, 1402, '/foo'))), + \sprintf('(%s)($|\,)', implode('|', array_fill(0, 1598, '/foo'))), + \sprintf('(%s)($|\,)', implode('|', array_fill(0, 1402, '/foo'))), ], ]; } diff --git a/Tests/VarnishXKeyPurgerTest.php b/Tests/VarnishXKeyPurgerTest.php index 0e029f1..b73dc0e 100644 --- a/Tests/VarnishXKeyPurgerTest.php +++ b/Tests/VarnishXKeyPurgerTest.php @@ -102,7 +102,7 @@ public function testCustomGlue(): void public function testItChunksHeaderToAvoidHittingVarnishLimit(int $maxHeaderLength, array $iris, array $keysToSend): void { /** @var HttpClientInterface $client */ - $client = new class() implements ClientInterface { + $client = new class implements ClientInterface { public array $sentKeys = []; public function send(RequestInterface $request, array $options = []): ResponseInterface diff --git a/VarnishPurger.php b/VarnishPurger.php index 6b4ddc2..a0a680b 100644 --- a/VarnishPurger.php +++ b/VarnishPurger.php @@ -93,7 +93,7 @@ private function purgeRequest(array $iris): void preg_quote($iri), $iris); foreach ($this->chunkRegexParts($parts) as $regex) { - $regex = sprintf(self::REGEXP_PATTERN, $regex); + $regex = \sprintf(self::REGEXP_PATTERN, $regex); $this->banRegex($regex); } } From b3a389fe2cee728fcc92db985dd3b504f48b5b50 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 09:26:44 +0200 Subject: [PATCH 04/40] chore: bump api-platform dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f775b48..7db3cd4 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ ], "require": { "php": ">=8.1", - "api-platform/metadata": "*@dev || ^3.1", - "api-platform/state": "*@dev || ^3.1", + "api-platform/metadata": "*@dev || ^3.2 || ^4.0", + "api-platform/state": "*@dev || ^3.2 || ^4.0", "symfony/http-foundation": "^6.4 || ^7.1" }, "require-dev": { From 1f05f65ef11336031408786466b9887369fdb438 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 12 Aug 2024 12:17:47 +0200 Subject: [PATCH 05/40] chore: update branch aliases with 3.4 and 4.0 (#6509) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7db3cd4..905aacb 100644 --- a/composer.json +++ b/composer.json @@ -62,4 +62,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} +} \ No newline at end of file From 60eb722995bac70d5dac929a10baa8efd8ea2486 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 16:36:55 +0200 Subject: [PATCH 06/40] cs: newline ending --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 905aacb..7db3cd4 100644 --- a/composer.json +++ b/composer.json @@ -62,4 +62,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} \ No newline at end of file +} From 56f57bd1cd842dfee854d77ca1bb30fe5bd16941 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 16:44:43 +0200 Subject: [PATCH 07/40] chore: align dependencies across components --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 7db3cd4..a97629a 100644 --- a/composer.json +++ b/composer.json @@ -21,16 +21,16 @@ ], "require": { "php": ">=8.1", - "api-platform/metadata": "*@dev || ^3.2 || ^4.0", - "api-platform/state": "*@dev || ^3.2 || ^4.0", + "api-platform/metadata": "@dev || ^3.2 || ^4.0", + "api-platform/state": "@dev || ^3.2 || ^4.0", "symfony/http-foundation": "^6.4 || ^7.1" }, "require-dev": { "guzzlehttp/guzzle": "^6.0 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.0", - "phpspec/prophecy-phpunit": "^2.0", + "symfony/dependency-injection": "^6.4 || ^7.1", + "phpspec/prophecy-phpunit": "^2.2", "symfony/phpunit-bridge": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.0" + "symfony/http-client": "^6.4 || ^7.1" }, "autoload": { "psr-4": { From cca39bf71f5b5f746fa53f97d8a79034c31d0118 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 19 Aug 2024 14:51:02 +0200 Subject: [PATCH 08/40] chore: remove @dev constraint (#6513) --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a97629a..05b88de 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ ], "require": { "php": ">=8.1", - "api-platform/metadata": "@dev || ^3.2 || ^4.0", - "api-platform/state": "@dev || ^3.2 || ^4.0", + "api-platform/metadata": "^3.2 || ^4.0", + "api-platform/state": "^3.2 || ^4.0", "symfony/http-foundation": "^6.4 || ^7.1" }, "require-dev": { From 7bd20fe11f1a6a66687581c528d38090d020b88a Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 6 Sep 2024 08:35:15 +0200 Subject: [PATCH 09/40] chore: remove phpunit-bridge (#6589) --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1d63036..80e65df 100644 --- a/composer.json +++ b/composer.json @@ -29,8 +29,8 @@ "guzzlehttp/guzzle": "^6.0 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.1", "phpspec/prophecy-phpunit": "^2.2", - "symfony/phpunit-bridge": "^6.4 || ^7.0", - "symfony/http-client": "^6.4 || ^7.1" + "symfony/http-client": "^6.4 || ^7.1", + "phpunit/phpunit": "^11.2" }, "autoload": { "psr-4": { From 8cdf94407d2dc185499854e392d3afc01f87718a Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 19 Sep 2024 17:56:20 +0200 Subject: [PATCH 10/40] chore: php version >= 8.2 (#6628) * chore: php version >= 8.2 * down to sf 7.0 * symfony extra require * missing hal constraint --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 80e65df..4ed63d1 100644 --- a/composer.json +++ b/composer.json @@ -20,16 +20,16 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", - "symfony/http-foundation": "^6.4 || ^7.1" + "symfony/http-foundation": "^6.4 || ^7.0" }, "require-dev": { "guzzlehttp/guzzle": "^6.0 || ^7.0", - "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/dependency-injection": "^6.4 || ^7.0", "phpspec/prophecy-phpunit": "^2.2", - "symfony/http-client": "^6.4 || ^7.1", + "symfony/http-client": "^6.4 || ^7.0", "phpunit/phpunit": "^11.2" }, "autoload": { @@ -56,7 +56,7 @@ "dev-3.4": "3.4.x-dev" }, "symfony": { - "require": "^6.4 || ^7.1" + "require": "^6.4 || ^7.0" } }, "scripts": { From 2af5d49bcaf79def01884c9ce08cfa75ca1ccacb Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 19 Sep 2024 17:58:51 +0200 Subject: [PATCH 11/40] chore: fix thanks url --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index a22acbd..6b42bca 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,10 @@ }, "symfony": { "require": "^6.4 || ^7.0" + }, + "thanks": { + "name": "api-platform/api-platform", + "url": "https://github.com/api-platform/api-platform" } }, "scripts": { From 8dc6afda3b9bd23270807c5a622ff10cc426dcdb Mon Sep 17 00:00:00 2001 From: Simon <1218015+simondaigre@users.noreply.github.com> Date: Sat, 26 Oct 2024 09:08:32 +0200 Subject: [PATCH 12/40] chore: missing .gitattributes (#6757) --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..801f208 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore From 66abd8e964f4d5ce1f91802d0ec2b1938abd0fab Mon Sep 17 00:00:00 2001 From: Simon <1218015+simondaigre@users.noreply.github.com> Date: Sat, 26 Oct 2024 09:08:32 +0200 Subject: [PATCH 13/40] chore: missing .gitattributes (#6757) --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..801f208 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/Tests export-ignore +/phpunit.xml.dist export-ignore From 118b11f9a14bc25e2fe3d745d91a1eb258e24126 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 28 Feb 2025 11:08:08 +0100 Subject: [PATCH 14/40] 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 15/40] 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 16/40] 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 17/40] 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 18/40] 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 19/40] 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 20/40] 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 21/40] 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 22/40] 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 23/40] 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 24/40] 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 25/40] 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 26/40] 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 27/40] 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 28/40] 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 29/40] 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 30/40] 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 31/40] 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 32/40] 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 33/40] 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 34/40] 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 35/40] 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 36/40] 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 37/40] 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 38/40] 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 39/40] 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 40/40] 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"