From cfa72784225e55ba9217f3f1218b0d7ab08feeeb Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Wed, 3 Dec 2025 14:52:16 +0100 Subject: [PATCH 1/2] fix(state): add support for the Deprecation link header --- .../Processor/AddLinkHeaderProcessor.php | 3 +- .../ApiResource/DeprecationHeader.php | 42 ++++++++++++++++++ tests/Functional/DeprecationHeaderTest.php | 44 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php create mode 100644 tests/Functional/DeprecationHeaderTest.php diff --git a/src/State/Processor/AddLinkHeaderProcessor.php b/src/State/Processor/AddLinkHeaderProcessor.php index 036213374e9..6a3c95b8306 100644 --- a/src/State/Processor/AddLinkHeaderProcessor.php +++ b/src/State/Processor/AddLinkHeaderProcessor.php @@ -52,7 +52,8 @@ public function process(mixed $data, Operation $operation, array $uriVariables = // We add our header here as Symfony does it only for the main Request and we want it to be done on errors (sub-request) as well $linksProvider = $request->attributes->get('_api_platform_links'); if ($this->serializer && ($links = $linksProvider?->getLinks())) { - $response->headers->set('Link', $this->serializer->serialize($links)); + $linkHeader = implode(',', array_filter([$this->serializer->serialize($links), $response->headers->get('Link')])); + $response->headers->set('Link', '' === $linkHeader ? null : $linkHeader); } $this->stopwatch?->stop('api_platform.processor.add_link_header'); diff --git a/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php b/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php new file mode 100644 index 00000000000..e7e3b3290d1 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php @@ -0,0 +1,42 @@ + + * + * 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\Tests\Fixtures\TestBundle\ApiResource; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\GetCollection; + +#[ApiResource( + operations: [new GetCollection(provider: [self::class, 'provide'])], + deprecationReason: 'This API is deprecated ', + headers: [ + 'deprecation' => '@1688169599', + 'sunset' => 'Sun, 30 Jun 2024 23:59:59 UTC', + 'link' => '; rel="deprecation"; type="text/html"', + ], +)] +class DeprecationHeader +{ + public function __construct(#[ApiProperty(identifier: true)] public int $id) + { + } + + public static function provide(): iterable + { + return [ + new self(1), + new self(2), + ]; + } +} diff --git a/tests/Functional/DeprecationHeaderTest.php b/tests/Functional/DeprecationHeaderTest.php new file mode 100644 index 00000000000..c90eb27784a --- /dev/null +++ b/tests/Functional/DeprecationHeaderTest.php @@ -0,0 +1,44 @@ + + * + * 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\Tests\Functional; + +use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\DeprecationHeader; +use ApiPlatform\Tests\SetupClassResourcesTrait; + +final class DeprecationHeaderTest extends ApiTestCase +{ + use SetupClassResourcesTrait; + + protected static ?bool $alwaysBootKernel = false; + + /** + * @return class-string[] + */ + public static function getResources(): array + { + return [DeprecationHeader::class]; + } + + public function testDeprecationHeader(): void + { + $response = self::createClient()->request('GET', '/deprecation_headers'); + + $headers = $response->getHeaders(); + + $this->assertContains('@1688169599', $headers['deprecation']); + $this->assertContains('Sun, 30 Jun 2024 23:59:59 UTC', $headers['sunset']); + $this->assertStringContainsString('; rel="deprecation"; type="text/html"', $headers['link'][0]); + } +} From 319e79b9b157142f3c5147308c769b5f36ba37a1 Mon Sep 17 00:00:00 2001 From: aaa2000 Date: Thu, 4 Dec 2025 17:34:03 +0100 Subject: [PATCH 2/2] use link parameter of operation instead of header parameter of ApiResource --- .../Processor/AddLinkHeaderProcessor.php | 3 +-- .../ApiResource/DeprecationHeader.php | 19 ++++++++++++------- tests/Functional/DeprecationHeaderTest.php | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/State/Processor/AddLinkHeaderProcessor.php b/src/State/Processor/AddLinkHeaderProcessor.php index 6a3c95b8306..036213374e9 100644 --- a/src/State/Processor/AddLinkHeaderProcessor.php +++ b/src/State/Processor/AddLinkHeaderProcessor.php @@ -52,8 +52,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables = // We add our header here as Symfony does it only for the main Request and we want it to be done on errors (sub-request) as well $linksProvider = $request->attributes->get('_api_platform_links'); if ($this->serializer && ($links = $linksProvider?->getLinks())) { - $linkHeader = implode(',', array_filter([$this->serializer->serialize($links), $response->headers->get('Link')])); - $response->headers->set('Link', '' === $linkHeader ? null : $linkHeader); + $response->headers->set('Link', $this->serializer->serialize($links)); } $this->stopwatch?->stop('api_platform.processor.add_link_header'); diff --git a/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php b/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php index e7e3b3290d1..fd114a5ddb2 100644 --- a/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php +++ b/tests/Fixtures/TestBundle/ApiResource/DeprecationHeader.php @@ -16,15 +16,20 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; +use Symfony\Component\WebLink\Link; #[ApiResource( - operations: [new GetCollection(provider: [self::class, 'provide'])], - deprecationReason: 'This API is deprecated ', - headers: [ - 'deprecation' => '@1688169599', - 'sunset' => 'Sun, 30 Jun 2024 23:59:59 UTC', - 'link' => '; rel="deprecation"; type="text/html"', - ], + operations: [new GetCollection( + headers: [ + 'deprecation' => '@1688169599', + 'sunset' => 'Sun, 30 Jun 2024 23:59:59 UTC', + ], + links: [ + new Link('deprecation', 'https://developer.example.com/deprecation'), + ], + deprecationReason: 'This API is deprecated', + provider: [self::class, 'provide'], + )], )] class DeprecationHeader { diff --git a/tests/Functional/DeprecationHeaderTest.php b/tests/Functional/DeprecationHeaderTest.php index c90eb27784a..bdebfe481e7 100644 --- a/tests/Functional/DeprecationHeaderTest.php +++ b/tests/Functional/DeprecationHeaderTest.php @@ -39,6 +39,6 @@ public function testDeprecationHeader(): void $this->assertContains('@1688169599', $headers['deprecation']); $this->assertContains('Sun, 30 Jun 2024 23:59:59 UTC', $headers['sunset']); - $this->assertStringContainsString('; rel="deprecation"; type="text/html"', $headers['link'][0]); + $this->assertStringContainsString('; rel="deprecation"', $headers['link'][0]); } }