diff --git a/src/Annotation/ApiResource.php b/src/Annotation/ApiResource.php
index 5f2a6ac1cf9..27a392564f9 100644
--- a/src/Annotation/ApiResource.php
+++ b/src/Annotation/ApiResource.php
@@ -73,6 +73,55 @@ final class ApiResource
{
use AttributesHydratorTrait;
+ /**
+ * @internal
+ *
+ * @see \ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\Configuration::addDefaultsSection
+ */
+ public const CONFIGURABLE_DEFAULTS = [
+ 'accessControl',
+ 'accessControlMessage',
+ 'security',
+ 'securityMessage',
+ 'securityPostDenormalize',
+ 'securityPostDenormalizeMessage',
+ 'cacheHeaders',
+ 'collectionOperations',
+ 'denormalizationContext',
+ 'deprecationReason',
+ 'description',
+ 'elasticsearch',
+ 'fetchPartial',
+ 'forceEager',
+ 'formats',
+ 'filters',
+ 'graphql',
+ 'hydraContext',
+ 'input',
+ 'iri',
+ 'itemOperations',
+ 'mercure',
+ 'messenger',
+ 'normalizationContext',
+ 'openapiContext',
+ 'order',
+ 'output',
+ 'paginationClientEnabled',
+ 'paginationClientItemsPerPage',
+ 'paginationClientPartial',
+ 'paginationEnabled',
+ 'paginationFetchJoinCollection',
+ 'paginationItemsPerPage',
+ 'maximumItemsPerPage',
+ 'paginationMaximumItemsPerPage',
+ 'paginationPartial',
+ 'paginationViaCursor',
+ 'routePrefix',
+ 'sunset',
+ 'swaggerContext',
+ 'validationGroups',
+ ];
+
/**
* @var string
*/
diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index cf39090b833..d0fcbaaa300 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -191,6 +191,29 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
if ($config['name_converter']) {
$container->setAlias('api_platform.name_converter', $config['name_converter']);
}
+ $container->setParameter('api_platform.defaults', $this->normalizeDefaults($config['defaults'] ?? []));
+ }
+
+ private function normalizeDefaults(array $defaults): array
+ {
+ $normalizedDefaults = ['attributes' => []];
+ $rootLevelOptions = [
+ 'description',
+ 'iri',
+ 'item_operations',
+ 'collection_operations',
+ 'graphql',
+ ];
+
+ foreach ($defaults as $option => $value) {
+ if (\in_array($option, $rootLevelOptions, true)) {
+ $normalizedDefaults[$option] = $value;
+ } else {
+ $normalizedDefaults['attributes'][$option] = $value;
+ }
+ }
+
+ return $normalizedDefaults;
}
private function registerMetadataConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php
index b49c43b28ed..ca8a5b0d4f0 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php
@@ -13,6 +13,7 @@
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection;
+use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\DocumentMetadata;
use ApiPlatform\Core\Exception\FilterValidationException;
use ApiPlatform\Core\Exception\InvalidArgumentException;
@@ -33,6 +34,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerExceptionInterface;
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
/**
* The configuration of the bundle.
@@ -128,13 +130,41 @@ public function getConfigTreeBuilder()
->canBeDisabled()
->addDefaultsIfNotSet()
->children()
- ->booleanNode('enabled')->defaultTrue()->info('To enable or disable pagination for all resource collections by default.')->end()
- ->booleanNode('partial')->defaultFalse()->info('To enable or disable partial pagination for all resource collections by default when pagination is enabled.')->end()
- ->booleanNode('client_enabled')->defaultFalse()->info('To allow the client to enable or disable the pagination.')->end()
- ->booleanNode('client_items_per_page')->defaultFalse()->info('To allow the client to set the number of items per page.')->end()
- ->booleanNode('client_partial')->defaultFalse()->info('To allow the client to enable or disable partial pagination.')->end()
- ->integerNode('items_per_page')->defaultValue(30)->info('The default number of items per page.')->end()
- ->integerNode('maximum_items_per_page')->defaultNull()->info('The maximum number of items per page.')->end()
+ ->booleanNode('enabled')
+ ->setDeprecated('The use of the `collection.pagination.enabled` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_enabled` instead.')
+ ->defaultTrue()
+ ->info('To enable or disable pagination for all resource collections by default.')
+ ->end()
+ ->booleanNode('partial')
+ ->setDeprecated('The use of the `collection.pagination.partial` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_partial` instead.')
+ ->defaultFalse()
+ ->info('To enable or disable partial pagination for all resource collections by default when pagination is enabled.')
+ ->end()
+ ->booleanNode('client_enabled')
+ ->setDeprecated('The use of the `collection.pagination.client_enabled` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_enabled` instead.')
+ ->defaultFalse()
+ ->info('To allow the client to enable or disable the pagination.')
+ ->end()
+ ->booleanNode('client_items_per_page')
+ ->setDeprecated('The use of the `collection.pagination.client_items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_items_per_page` instead.')
+ ->defaultFalse()
+ ->info('To allow the client to set the number of items per page.')
+ ->end()
+ ->booleanNode('client_partial')
+ ->setDeprecated('The use of the `collection.pagination.client_partial` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_client_partial` instead.')
+ ->defaultFalse()
+ ->info('To allow the client to enable or disable partial pagination.')
+ ->end()
+ ->integerNode('items_per_page')
+ ->setDeprecated('The use of the `collection.pagination.items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_items_per_page` instead.')
+ ->defaultValue(30)
+ ->info('The default number of items per page.')
+ ->end()
+ ->integerNode('maximum_items_per_page')
+ ->setDeprecated('The use of the `collection.pagination.maximum_items_per_page` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.pagination_maximum_items_per_page` instead.')
+ ->defaultNull()
+ ->info('The maximum number of items per page.')
+ ->end()
->scalarNode('page_parameter_name')->defaultValue('page')->cannotBeEmpty()->info('The default name of the parameter handling the page number.')->end()
->scalarNode('enabled_parameter_name')->defaultValue('pagination')->cannotBeEmpty()->info('The name of the query parameter to enable or disable pagination.')->end()
->scalarNode('items_per_page_parameter_name')->defaultValue('itemsPerPage')->cannotBeEmpty()->info('The name of the query parameter to set the number of items per page.')->end()
@@ -179,6 +209,8 @@ public function getConfigTreeBuilder()
'jsonld' => ['mime_types' => ['application/ld+json']],
]);
+ $this->addDefaultsSection($rootNode);
+
return $treeBuilder;
}
@@ -311,16 +343,30 @@ private function addHttpCacheSection(ArrayNodeDefinition $rootNode): void
->arrayNode('http_cache')
->addDefaultsIfNotSet()
->children()
- ->booleanNode('etag')->defaultTrue()->info('Automatically generate etags for API responses.')->end()
- ->integerNode('max_age')->defaultNull()->info('Default value for the response max age.')->end()
- ->integerNode('shared_max_age')->defaultNull()->info('Default value for the response shared (proxy) max age.')->end()
+ ->booleanNode('etag')
+ ->setDeprecated('The use of the `http_cache.etag` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.etag` instead.')
+ ->defaultTrue()
+ ->info('Automatically generate etags for API responses.')
+ ->end()
+ ->integerNode('max_age')
+ ->setDeprecated('The use of the `http_cache.max_age` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.max_age` instead.')
+ ->defaultNull()
+ ->info('Default value for the response max age.')
+ ->end()
+ ->integerNode('shared_max_age')
+ ->setDeprecated('The use of the `http_cache.shared_max_age` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.shared_max_age` instead.')
+ ->defaultNull()
+ ->info('Default value for the response shared (proxy) max age.')
+ ->end()
->arrayNode('vary')
+ ->setDeprecated('The use of the `http_cache.vary` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.vary` instead.')
->defaultValue(['Accept'])
->prototype('scalar')->end()
->info('Default values of the "Vary" HTTP header.')
->end()
->booleanNode('public')->defaultNull()->info('To make all responses public by default.')->end()
->arrayNode('invalidation')
+ ->setDeprecated('The use of the `http_cache.invalidation` has been deprecated in 2.6 and will be removed in 3.0. Use `defaults.cache_headers.invalidation` instead.')
->info('Enable the tags-based cache invalidation system.')
->canBeEnabled()
->children()
@@ -494,4 +540,28 @@ private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, ar
->end()
->end();
}
+
+ private function addDefaultsSection(ArrayNodeDefinition $rootNode): void
+ {
+ $nameConverter = new CamelCaseToSnakeCaseNameConverter();
+ $defaultsNode = $rootNode->children()->arrayNode('defaults');
+
+ $defaultsNode
+ ->ignoreExtraKeys()
+ ->beforeNormalization()
+ ->always(function (array $defaults) use ($nameConverter) {
+ $normalizedDefaults = [];
+ foreach ($defaults as $option => $value) {
+ $option = $nameConverter->normalize($option);
+ $normalizedDefaults[$option] = $value;
+ }
+
+ return $normalizedDefaults;
+ });
+
+ foreach (ApiResource::CONFIGURABLE_DEFAULTS as $attribute) {
+ $snakeCased = $nameConverter->normalize($attribute);
+ $defaultsNode->children()->variableNode($snakeCased);
+ }
+ }
}
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml
index 70556d4f8bc..6440d8c921e 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/annotation.xml
@@ -14,6 +14,7 @@
+ %api_platform.defaults%
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml
index 1657eb99b86..7344a04a22e 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml
@@ -18,6 +18,7 @@