Skip to content

Commit a62a077

Browse files
authored
Add support for PATCH, refactor formats detection (#2895)
* Add support for PATCH, refactor formats detection * Migrate AddFormatListener * Fix more deprecations * Fix some more deprecations, and tests * Fix more deprecations * Fix all remaining deprecations * Fix a Behat test * Fix PHPStan * More deprecations * fix build * Fix MongoDB test * Input / output formats * Set skip_null_values to true by default if PATCH is enabled * fix OperationResourceMetadataFactoryTest * Use in/out formats parameters * Fix more tests * Fix Behat tests * All green! * Add support for input_formats in DocumentationNormalizer * Last commit? :) * Remove dead code * Add a unit test for FormatsResourceMetadataFactory
1 parent 8b92034 commit a62a077

File tree

45 files changed

+1419
-770
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1419
-770
lines changed

src/Api/FormatsProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
* {@inheritdoc}
2121
*
2222
* @author Anthony GRASSIOT <[email protected]>
23+
*
24+
* @deprecated since API Platform 2.5, use the "formats" attribute instead
2325
*/
2426
final class FormatsProvider implements FormatsProviderInterface, OperationAwareFormatsProviderInterface
2527
{
@@ -28,6 +30,8 @@ final class FormatsProvider implements FormatsProviderInterface, OperationAwareF
2830

2931
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $configuredFormats)
3032
{
33+
@trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "formats" attribute instead.', __CLASS__), E_USER_DEPRECATED);
34+
3135
$this->resourceMetadataFactory = $resourceMetadataFactory;
3236
$this->configuredFormats = $configuredFormats;
3337
}

src/Api/FormatsProviderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* Extracts formats for a given operation according to the retrieved Metadata.
1818
*
1919
* @author Anthony GRASSIOT <[email protected]>
20+
*
21+
* @deprecated since API Platform 2.5, use the "formats" attribute instead
2022
*/
2123
interface FormatsProviderInterface
2224
{

src/Api/OperationAwareFormatsProviderInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* Extracts formats for a given operation according to the retrieved Metadata.
1818
*
1919
* @author Anthony GRASSIOT <[email protected]>
20+
*
21+
* @deprecated since API Platform 2.5, use the "formats" attribute instead
2022
*/
2123
interface OperationAwareFormatsProviderInterface extends FormatsProviderInterface
2224
{

src/Api/OperationMethodResolverInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* Resolves the uppercased HTTP method associated with an operation.
2020
*
2121
* @author Kévin Dunglas <[email protected]>
22+
*
23+
* @deprecated since API Platform 2.5, use the "method" attribute instead
2224
*/
2325
interface OperationMethodResolverInterface
2426
{

src/Bridge/Symfony/Bundle/Action/SwaggerUiAction.php

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
use ApiPlatform\Core\Api\FormatsProviderInterface;
1717
use ApiPlatform\Core\Documentation\Documentation;
18-
use ApiPlatform\Core\Exception\InvalidArgumentException;
1918
use ApiPlatform\Core\Exception\RuntimeException;
2019
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2120
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
@@ -42,7 +41,7 @@ final class SwaggerUiAction
4241
private $description;
4342
private $version;
4443
private $showWebby;
45-
private $formats = [];
44+
private $formats;
4645
private $oauthEnabled;
4746
private $oauthClientId;
4847
private $oauthClientSecret;
@@ -56,10 +55,7 @@ final class SwaggerUiAction
5655
private $reDocEnabled;
5756
private $graphqlEnabled;
5857

59-
/**
60-
* @throws InvalidArgumentException
61-
*/
62-
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', /* FormatsProviderInterface */ $formatsProvider = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false)
58+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, TwigEnvironment $twig, UrlGeneratorInterface $urlGenerator, string $title = '', string $description = '', string $version = '', $formats = [], $oauthEnabled = false, $oauthClientId = '', $oauthClientSecret = '', $oauthType = '', $oauthFlow = '', $oauthTokenUrl = '', $oauthAuthorizationUrl = '', $oauthScopes = [], bool $showWebby = true, bool $swaggerUiEnabled = false, bool $reDocEnabled = false, bool $graphqlEnabled = false)
6359
{
6460
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
6561
$this->resourceMetadataFactory = $resourceMetadataFactory;
@@ -82,32 +78,33 @@ public function __construct(ResourceNameCollectionFactoryInterface $resourceName
8278
$this->reDocEnabled = $reDocEnabled;
8379
$this->graphqlEnabled = $graphqlEnabled;
8480

85-
if (\is_array($formatsProvider)) {
86-
if ($formatsProvider) {
87-
// Only trigger notification for non-default argument
88-
@trigger_error('Using an array as formats provider is deprecated since API Platform 2.3 and will not be possible anymore in API Platform 3', E_USER_DEPRECATED);
89-
}
90-
$this->formats = $formatsProvider;
81+
if (\is_array($formats)) {
82+
$this->formats = $formats;
9183

9284
return;
9385
}
94-
if (!$formatsProvider instanceof FormatsProviderInterface) {
95-
throw new InvalidArgumentException(sprintf('The "$formatsProvider" argument is expected to be an implementation of the "%s" interface.', FormatsProviderInterface::class));
96-
}
9786

98-
$this->formatsProvider = $formatsProvider;
87+
@trigger_error(sprintf('Passing an array or an instance of "%s" as 5th parameter of the constructor of "%s" is deprecated since API Platform 2.5, pass an array instead', FormatsProviderInterface::class, __CLASS__), E_USER_DEPRECATED);
88+
$this->formatsProvider = $formats;
9989
}
10090

10191
public function __invoke(Request $request)
10292
{
93+
$attributes = RequestAttributesExtractor::extractAttributes($request);
94+
10395
// BC check to be removed in 3.0
104-
if (null !== $this->formatsProvider) {
105-
$this->formats = $this->formatsProvider->getFormatsFromAttributes(RequestAttributesExtractor::extractAttributes($request));
96+
if (null === $this->formatsProvider) {
97+
$formats = $attributes ? $this
98+
->resourceMetadataFactory
99+
->create($attributes['resource_class'])
100+
->getOperationAttribute($attributes, 'output_formats', [], true) : $this->formats;
101+
} else {
102+
$formats = $this->formatsProvider->getFormatsFromAttributes($attributes);
106103
}
107104

108-
$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version, $this->formats);
105+
$documentation = new Documentation($this->resourceNameCollectionFactory->create(), $this->title, $this->description, $this->version);
109106

110-
return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation)));
107+
return new Response($this->twig->render('@ApiPlatform/SwaggerUi/index.html.twig', $this->getContext($request, $documentation) + ['formats' => $formats]));
111108
}
112109

113110
/**
@@ -118,7 +115,6 @@ private function getContext(Request $request, Documentation $documentation): arr
118115
$context = [
119116
'title' => $this->title,
120117
'description' => $this->description,
121-
'formats' => $this->formats,
122118
'showWebby' => $this->showWebby,
123119
'swaggerUiEnabled' => $this->swaggerUiEnabled,
124120
'reDocEnabled' => $this->reDocEnabled,

src/Bridge/Symfony/Bundle/Command/SwaggerCommand.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ final class SwaggerCommand extends Command
4040
private $apiVersion;
4141
private $apiFormats;
4242

43-
public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats)
43+
public function __construct(NormalizerInterface $normalizer, ResourceNameCollectionFactoryInterface $resourceNameCollection, string $apiTitle, string $apiDescription, string $apiVersion, array $apiFormats = null)
4444
{
4545
$this->normalizer = $normalizer;
4646
$this->resourceNameCollectionFactory = $resourceNameCollection;
@@ -49,6 +49,10 @@ public function __construct(NormalizerInterface $normalizer, ResourceNameCollect
4949
$this->apiVersion = $apiVersion;
5050
$this->apiFormats = $apiFormats;
5151

52+
if (null !== $apiFormats) {
53+
@trigger_error(sprintf('Passing a 6th parameter to the constructor of "%s" is deprecated since API Platform 2.5', __CLASS__), E_USER_DEPRECATED);
54+
}
55+
5256
parent::__construct();
5357
}
5458

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,15 @@ public function load(array $configs, ContainerBuilder $container): void
8787
$config = $this->processConfiguration($configuration, $configs);
8888

8989
$formats = $this->getFormats($config['formats']);
90+
$patchFormats = $this->getFormats($config['patch_formats']);
9091
$errorFormats = $this->getFormats($config['error_formats']);
9192

92-
$this->registerCommonConfiguration($container, $config, $loader, $formats, $errorFormats);
93+
// Backward Compatibility layer
94+
if (isset($formats['jsonapi']) && !isset($patchFormats['jsonapi'])) {
95+
$patchFormats['jsonapi'] = ['application/vnd.api+json'];
96+
}
97+
98+
$this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats);
9399
$this->registerMetadataConfiguration($container, $config, $loader);
94100
$this->registerOAuthConfiguration($container, $config);
95101
$this->registerSwaggerConfiguration($container, $config, $loader);
@@ -127,7 +133,7 @@ public function load(array $configs, ContainerBuilder $container): void
127133
}
128134
}
129135

130-
private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $errorFormats): void
136+
private function registerCommonConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader, array $formats, array $patchFormats, array $errorFormats): void
131137
{
132138
$loader->load('api.xml');
133139
$loader->load('data_persister.xml');
@@ -146,6 +152,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
146152
$container->setParameter('api_platform.show_webby', $config['show_webby']);
147153
$container->setParameter('api_platform.exception_to_status', $config['exception_to_status']);
148154
$container->setParameter('api_platform.formats', $formats);
155+
$container->setParameter('api_platform.patch_formats', $patchFormats);
149156
$container->setParameter('api_platform.error_formats', $errorFormats);
150157
$container->setParameter('api_platform.allow_plain_identifiers', $config['allow_plain_identifiers']);
151158
$container->setParameter('api_platform.eager_loading.enabled', $this->isConfigEnabled($container, $config['eager_loading']));

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ public function getConfigTreeBuilder()
163163
'json' => ['mime_types' => ['application/json']], // Swagger support
164164
'html' => ['mime_types' => ['text/html']], // Swagger UI support
165165
]);
166+
$this->addFormatSection($rootNode, 'patch_formats', []);
166167
$this->addFormatSection($rootNode, 'error_formats', [
167168
'jsonproblem' => ['mime_types' => ['application/problem+json']],
168169
'jsonld' => ['mime_types' => ['application/ld+json']],

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<service id="api_platform.operation_method_resolver" class="ApiPlatform\Core\Bridge\Symfony\Routing\OperationMethodResolver" public="false">
1919
<argument type="service" id="api_platform.router" />
2020
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
21+
<deprecated>The "%service_id%" service is deprecated since API Platform 2.5.</deprecated>
2122
</service>
2223

2324
<service id="api_platform.route_name_resolver" class="ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver" public="false">
@@ -68,13 +69,18 @@
6869
<service id="api_platform.formats_provider" class="ApiPlatform\Core\Api\FormatsProvider">
6970
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
7071
<argument>%api_platform.formats%</argument>
72+
<deprecated>The "%service_id%" service is deprecated since API Platform 2.5.</deprecated>
73+
</service>
74+
75+
<service id="ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface" alias="api_platform.formats_provider">
76+
<deprecated>The "%alias_id%" alias is deprecated since API Platform 2.5.</deprecated>
7177
</service>
72-
<service id="ApiPlatform\Core\Api\OperationAwareFormatsProviderInterface" alias="api_platform.formats_provider" />
7378

7479
<!-- Serializer -->
7580

7681
<service id="api_platform.serializer.context_builder" class="ApiPlatform\Core\Serializer\SerializerContextBuilder" public="false">
7782
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
83+
<argument>%api_platform.patch_formats%</argument>
7884
</service>
7985
<service id="ApiPlatform\Core\Serializer\SerializerContextBuilderInterface" alias="api_platform.serializer.context_builder" />
8086

@@ -150,7 +156,8 @@
150156
<!-- kernel.request priority must be < 8 to be executed after the Firewall -->
151157
<service id="api_platform.listener.request.add_format" class="ApiPlatform\Core\EventListener\AddFormatListener">
152158
<argument type="service" id="api_platform.negotiator" />
153-
<argument type="service" id="api_platform.formats_provider" />
159+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
160+
<argument>%api_platform.formats%</argument>
154161

155162
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="7" />
156163
</service>
@@ -177,7 +184,6 @@
177184
<service id="api_platform.listener.request.deserialize" class="ApiPlatform\Core\EventListener\DeserializeListener">
178185
<argument type="service" id="api_platform.serializer" />
179186
<argument type="service" id="api_platform.serializer.context_builder" />
180-
<argument type="service" id="api_platform.formats_provider" />
181187
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
182188

183189
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="2" />
@@ -231,7 +237,6 @@
231237
<argument>%api_platform.title%</argument>
232238
<argument>%api_platform.description%</argument>
233239
<argument>%api_platform.version%</argument>
234-
<argument type="service" id="api_platform.formats_provider" />
235240
</service>
236241

237242
<service id="api_platform.action.exception" class="ApiPlatform\Core\Action\ExceptionAction" public="true">

src/Bridge/Symfony/Bundle/Resources/config/hydra.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
1111
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
1212
<argument type="service" id="api_platform.resource_class_resolver" />
13-
<argument type="service" id="api_platform.operation_method_resolver" />
13+
<argument>null</argument>
1414
<argument type="service" id="api_platform.router" />
1515
<argument type="service" id="api_platform.subresource_operation_factory" />
1616
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />

src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@
2424

2525
<service id="api_platform.metadata.resource.metadata_factory.operation" class="ApiPlatform\Core\Metadata\Resource\Factory\OperationResourceMetadataFactory" decorates="api_platform.metadata.resource.metadata_factory" decoration-priority="10" public="false">
2626
<argument type="service" id="api_platform.metadata.resource.metadata_factory.operation.inner" />
27+
<argument>%api_platform.patch_formats%</argument>
28+
</service>
29+
30+
<service id="api_platform.metadata.resource.metadata_factory.formats" class="ApiPlatform\Core\Metadata\Resource\Factory\FormatsResourceMetadataFactory" decorates="api_platform.metadata.resource.metadata_factory" decoration-priority="5" public="false">
31+
<argument type="service" id="api_platform.metadata.resource.metadata_factory.formats.inner" />
2732
<argument>%api_platform.formats%</argument>
33+
<argument>%api_platform.patch_formats%</argument>
2834
</service>
2935

3036
<service id="api_platform.metadata.resource.metadata_factory.cached" class="ApiPlatform\Core\Metadata\Resource\Factory\CachedResourceMetadataFactory" decorates="api_platform.metadata.resource.metadata_factory" decoration-priority="-10" public="false">

src/Bridge/Symfony/Bundle/Resources/config/swagger-ui.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<argument>%api_platform.title%</argument>
2020
<argument>%api_platform.description%</argument>
2121
<argument>%api_platform.version%</argument>
22-
<argument type="service" id="api_platform.formats_provider" />
22+
<argument>%api_platform.formats%</argument>
2323
<argument>%api_platform.oauth.enabled%</argument>
2424
<argument>%api_platform.oauth.clientId%</argument>
2525
<argument>%api_platform.oauth.clientSecret%</argument>

src/Bridge/Symfony/Bundle/Resources/config/swagger.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
1212
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
1313
<argument type="service" id="api_platform.resource_class_resolver" />
14-
<argument type="service" id="api_platform.operation_method_resolver" />
14+
<argument>null</argument>
1515
<argument type="service" id="api_platform.operation_path_resolver" />
1616
<argument>null</argument>
1717
<argument type="service" id="api_platform.filter_locator" />
@@ -28,7 +28,7 @@
2828
<argument>%api_platform.collection.pagination.page_parameter_name%</argument>
2929
<argument>%api_platform.collection.pagination.client_items_per_page%</argument>
3030
<argument>%api_platform.collection.pagination.items_per_page_parameter_name%</argument>
31-
<argument type="service" id="api_platform.formats_provider" on-invalid="ignore" />
31+
<argument>%api_platform.formats%</argument>
3232
<argument>%api_platform.collection.pagination.client_enabled%</argument>
3333
<argument>%api_platform.collection.pagination.enabled_parameter_name%</argument>
3434
<tag name="serializer.normalizer" priority="-790" />
@@ -45,7 +45,6 @@
4545
<argument>%api_platform.title%</argument>
4646
<argument>%api_platform.description%</argument>
4747
<argument>%api_platform.version%</argument>
48-
<argument>%api_platform.formats%</argument>
4948
<tag name="console.command" />
5049
</service>
5150

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas
186186
$resourceShortName = $resourceMetadata->getShortName();
187187

188188
if (isset($operation['route_name'])) {
189+
if (!isset($operation['method'])) {
190+
@trigger_error(sprintf('Not setting the "method" attribute is deprecated and will not be supported anymore in API Platform 3.0, set it for the %s operation "%s" of the class "%s".', OperationType::COLLECTION === $operationType ? 'collection' : 'item', $operationName, $resourceClass), E_USER_DEPRECATED);
191+
}
192+
189193
return;
190194
}
191195

src/Bridge/Symfony/Routing/OperationMethodResolver.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
*
2626
* @author Kévin Dunglas <[email protected]>
2727
* @author Teoh Han Hui <[email protected]>
28+
*
29+
* @deprecated since API Platform 2.5, use the "method" attribute instead
2830
*/
2931
final class OperationMethodResolver implements OperationMethodResolverInterface
3032
{
@@ -33,6 +35,8 @@ final class OperationMethodResolver implements OperationMethodResolverInterface
3335

3436
public function __construct(RouterInterface $router, ResourceMetadataFactoryInterface $resourceMetadataFactory)
3537
{
38+
@trigger_error(sprintf('The "%s" class is deprecated since API Platform 2.5, use the "method" attribute instead.', __CLASS__), E_USER_DEPRECATED);
39+
3640
$this->router = $router;
3741
$this->resourceMetadataFactory = $resourceMetadataFactory;
3842
}

src/Bridge/Symfony/Routing/OperationMethodResolverInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
* Resolves the HTTP method associated with an operation, extended for Symfony routing.
2222
*
2323
* @author Teoh Han Hui <[email protected]>
24+
*
25+
* @deprecated since API Platform 2.5, use the "method" attribute instead
2426
*/
2527
interface OperationMethodResolverInterface extends BaseOperationMethodResolverInterface
2628
{

0 commit comments

Comments
 (0)