Skip to content

Commit 45b5526

Browse files
authored
fix(metadata): _format broken bc promise on #5080 (#5187)
* fix(metadata): _format broken bc promise on #5080 * add deprecation contracts * fix phpstan
1 parent 148442c commit 45b5526

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"doctrine/inflector": "^1.0 || ^2.0",
2424
"psr/cache": "^1.0 || ^2.0 || ^3.0",
2525
"psr/container": "^1.0 || ^2.0",
26+
"symfony/deprecation-contracts": "^3.1",
2627
"symfony/http-foundation": "^6.1",
2728
"symfony/http-kernel": "^6.1",
2829
"symfony/property-access": "^6.1",

src/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactory.php

+14-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
*/
2828
final class UriTemplateResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
2929
{
30+
private $triggerLegacyFormatOnce = [];
31+
3032
public function __construct(private readonly LinkFactoryInterface $linkFactory, private readonly PathSegmentNameGeneratorInterface $pathSegmentNameGenerator, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null)
3133
{
3234
}
@@ -95,6 +97,16 @@ private function generateUriTemplate(HttpOperation $operation): string
9597
{
9698
$uriTemplate = $operation->getUriTemplate() ?? sprintf('/%s', $this->pathSegmentNameGenerator->getSegmentName($operation->getShortName()));
9799
$uriVariables = $operation->getUriVariables() ?? [];
100+
$legacyFormat = null;
101+
102+
if (str_ends_with($uriTemplate, '{._format}') || ($legacyFormat = str_ends_with($uriTemplate, '.{_format}'))) {
103+
$uriTemplate = substr($uriTemplate, 0, -10);
104+
}
105+
106+
if ($legacyFormat && ($this->triggerLegacyFormatOnce[$operation->getClass()] ?? true)) {
107+
$this->triggerLegacyFormatOnce[$operation->getClass()] = false;
108+
trigger_deprecation('api-platform/core', '3.0', sprintf('The special Symfony parameter ".{_format}" in your URI Template is deprecated, use an RFC6570 variable "{._format}" on the class "%s" instead. We will only use the RFC6570 compatible variable in 4.0.', $operation->getClass()));
109+
}
98110

99111
if ($parameters = array_keys($uriVariables)) {
100112
foreach ($parameters as $parameterName) {
@@ -105,7 +117,7 @@ private function generateUriTemplate(HttpOperation $operation): string
105117
}
106118
}
107119

108-
return sprintf('%s{._format}', $uriTemplate);
120+
return sprintf('%s%s', $uriTemplate, $legacyFormat ? '.{_format}' : '{._format}');
109121
}
110122

111123
private function configureUriVariables(ApiResource|HttpOperation $operation): ApiResource|HttpOperation
@@ -144,7 +156,7 @@ private function configureUriVariables(ApiResource|HttpOperation $operation): Ap
144156
}
145157
$operation = $operation->withUriVariables($uriVariables);
146158

147-
if (str_ends_with($uriTemplate, '{._format}')) {
159+
if (str_ends_with($uriTemplate, '{._format}') || str_ends_with($uriTemplate, '.{_format}')) {
148160
$uriTemplate = substr($uriTemplate, 0, -10);
149161
}
150162

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResourceNotLoaded;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
18+
#[ApiResource('/format_not_rfc.{_format}')]
19+
class SymfonyFormatParameterLegacy
20+
{
21+
}

tests/Metadata/Resource/Factory/UriTemplateResourceMetadataCollectionFactoryTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,26 @@
2323
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2424
use ApiPlatform\Metadata\Property\PropertyNameCollection;
2525
use ApiPlatform\Metadata\Put;
26+
use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
2627
use ApiPlatform\Metadata\Resource\Factory\LinkFactory;
2728
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2829
use ApiPlatform\Metadata\Resource\Factory\UriTemplateResourceMetadataCollectionFactory;
2930
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
3031
use ApiPlatform\Operation\PathSegmentNameGeneratorInterface;
32+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResourceNotLoaded\SymfonyFormatParameterLegacy;
3133
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\AttributeResource;
3234
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
3335
use PHPUnit\Framework\TestCase;
3436
use Prophecy\Argument;
3537
use Prophecy\PhpUnit\ProphecyTrait;
38+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
3639

3740
/**
3841
* @author Antoine Bluchet <[email protected]>
3942
*/
4043
class UriTemplateResourceMetadataCollectionFactoryTest extends TestCase
4144
{
45+
use ExpectDeprecationTrait;
4246
use ProphecyTrait;
4347

4448
public function testCreate(): void
@@ -169,4 +173,25 @@ class: AttributeResource::class,
169173
$uriTemplateResourceMetadataCollectionFactory->create(AttributeResource::class)
170174
);
171175
}
176+
177+
/**
178+
* @group legacy
179+
*/
180+
public function testCreateWithLegacyFormat(): void
181+
{
182+
$this->expectDeprecation('Since api-platform/core 3.0: The special Symfony parameter ".{_format}" in your URI Template is deprecated, use an RFC6570 variable "{._format}" on the class "ApiPlatform\Tests\Fixtures\TestBundle\ApiResourceNotLoaded\SymfonyFormatParameterLegacy" instead. We will only use the RFC6570 compatible variable in 4.0.');
183+
184+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
185+
$propertyNameCollectionFactoryProphecy->create(Argument::cetera())->willReturn(new PropertyNameCollection());
186+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
187+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
188+
$linkFactory = new LinkFactory($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal());
189+
$pathSegmentNameGeneratorProphecy = $this->prophesize(PathSegmentNameGeneratorInterface::class);
190+
$pathSegmentNameGeneratorProphecy->getSegmentName('SymfonyFormatParameterLegacy')->willReturn('attribute_resources');
191+
$resourceCollectionMetadataFactoryProphecy = new AttributesResourceMetadataCollectionFactory();
192+
193+
$linkFactory = new LinkFactory($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceClassResolverProphecy->reveal());
194+
$uriTemplateResourceMetadataCollectionFactory = new UriTemplateResourceMetadataCollectionFactory($linkFactory, $pathSegmentNameGeneratorProphecy->reveal(), $resourceCollectionMetadataFactoryProphecy);
195+
$uriTemplateResourceMetadataCollectionFactory->create(SymfonyFormatParameterLegacy::class);
196+
}
172197
}

0 commit comments

Comments
 (0)