diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index cf39090b833..8b5ef60d791 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -191,6 +191,7 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
if ($config['name_converter']) {
$container->setAlias('api_platform.name_converter', $config['name_converter']);
}
+ $container->setParameter('api_platform.defaults', $config['defaults']);
}
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..01a66ee3b80 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;
@@ -179,6 +180,8 @@ public function getConfigTreeBuilder()
'jsonld' => ['mime_types' => ['application/ld+json']],
]);
+ $this->addDefaultsSection($rootNode);
+
return $treeBuilder;
}
@@ -494,4 +497,21 @@ private function addFormatSection(ArrayNodeDefinition $rootNode, string $key, ar
->end()
->end();
}
+
+ private function addDefaultsSection(ArrayNodeDefinition $rootNode): void
+ {
+ $defaultsNode = $rootNode->children()->arrayNode('defaults')->addDefaultsIfNotSet();
+ $attributesNode = $defaultsNode->children()->arrayNode('attributes')->addDefaultsIfNotSet();
+ $properties = (new \ReflectionClass(ApiResource::class))->getProperties();
+
+ foreach ($properties as $property) {
+ $optionName = $property->getName();
+ if ('attributes' === $optionName) { // "attributes" is a sub-level and not a config option
+ continue;
+ }
+
+ $node = $property->isPublic() ? $defaultsNode : $attributesNode;
+ $node->children()->variableNode($optionName);
+ }
+ }
}
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml
index 1a50a9b0e14..b9ef44f00c2 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml
@@ -33,6 +33,11 @@
%api_platform.patch_formats%
+
+
+ %api_platform.defaults%
+
+
diff --git a/src/Metadata/Resource/Factory/DefaultsResourceMetadataFactory.php b/src/Metadata/Resource/Factory/DefaultsResourceMetadataFactory.php
new file mode 100644
index 00000000000..7d490ee51f4
--- /dev/null
+++ b/src/Metadata/Resource/Factory/DefaultsResourceMetadataFactory.php
@@ -0,0 +1,65 @@
+
+ *
+ * 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\Core\Metadata\Resource\Factory;
+
+use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
+
+/**
+ * Creates a resource metadata from the default configuration.
+ *
+ * @author Beno!t POLASZEK
+ */
+final class DefaultsResourceMetadataFactory implements ResourceMetadataFactoryInterface
+{
+ private $decorated;
+
+ private $defaults;
+
+ public function __construct(ResourceMetadataFactoryInterface $decorated, array $defaults)
+ {
+ $this->decorated = $decorated;
+ $this->defaults = $defaults;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function create(string $resourceClass): ResourceMetadata
+ {
+ $resourceMetadata = $this->decorated->create($resourceClass);
+ $attributes = $resourceMetadata->getAttributes();
+ $attributesConfig = $this->defaults['attributes'] ?? [];
+
+ foreach ($this->defaults as $key => $value) {
+ if ('attributes' === $key) {
+ continue;
+ }
+
+ if (method_exists($resourceMetadata, 'get'.$key) && null === $resourceMetadata->{'get'.$key}() && method_exists($resourceMetadata, 'with'.$key)) {
+ $resourceMetadata = $resourceMetadata->{'with'.$key}($value);
+ }
+ }
+
+ foreach ($attributesConfig as $key => $value) {
+ if (\array_key_exists($key, $attributes)) {
+ continue;
+ }
+
+ $attributes[$key] = $value;
+ $resourceMetadata = $resourceMetadata->withAttributes($attributes);
+ }
+
+ return $resourceMetadata;
+ }
+}
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index 11f23d41b3f..7bb63186306 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -144,6 +144,9 @@ class ApiPlatformExtensionTest extends TestCase
'doctrine_mongodb_odm' => [
'enabled' => false,
],
+ 'defaults' => [
+ 'attributes' => [],
+ ],
]];
private $extension;
@@ -694,6 +697,7 @@ public function testEnableElasticsearch()
$containerBuilderProphecy->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class)->willReturn($this->childDefinitionProphecy)->shouldBeCalled();
$containerBuilderProphecy->setParameter('api_platform.elasticsearch.hosts', ['http://elasticsearch:9200'])->shouldBeCalled();
$containerBuilderProphecy->setParameter('api_platform.elasticsearch.mapping', [])->shouldBeCalled();
+ $containerBuilderProphecy->setParameter('api_platform.defaults', ['attributes' => []])->shouldBeCalled();
$config = self::DEFAULT_CONFIG;
$config['api_platform']['elasticsearch'] = [
@@ -804,6 +808,7 @@ private function getPartialContainerBuilderProphecy()
'api_platform.http_cache.shared_max_age' => null,
'api_platform.http_cache.vary' => ['Accept'],
'api_platform.http_cache.public' => null,
+ 'api_platform.defaults' => ['attributes' => []],
'api_platform.enable_entrypoint' => true,
'api_platform.enable_docs' => true,
];
@@ -885,6 +890,7 @@ private function getPartialContainerBuilderProphecy()
'api_platform.metadata.property.name_collection_factory.property_info',
'api_platform.metadata.property.name_collection_factory.xml',
'api_platform.metadata.resource.metadata_factory.cached',
+ 'api_platform.metadata.resource.metadata_factory.defaults',
'api_platform.metadata.resource.metadata_factory.operation',
'api_platform.metadata.resource.metadata_factory.formats',
'api_platform.metadata.resource.metadata_factory.input_output',
@@ -1076,6 +1082,7 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
'api_platform.resource_class_directories' => Argument::type('array'),
'api_platform.validator.serialize_payload_fields' => [],
'api_platform.elasticsearch.enabled' => false,
+ 'api_platform.defaults' => ['attributes' => []],
];
foreach ($parameters as $key => $value) {
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
index a891c8724e2..39e07400a81 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php
@@ -204,6 +204,9 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm
],
'allow_plain_identifiers' => false,
'resource_class_directories' => [],
+ 'defaults' => [
+ 'attributes' => [],
+ ],
], $config);
}
@@ -402,4 +405,21 @@ public function testEnableElasticsearch()
$this->assertTrue($config['elasticsearch']['enabled']);
}
+
+ public function testDefaultsConfig()
+ {
+ $config = $this->processor->processConfiguration($this->configuration, [
+ 'api_platform' => [
+ 'defaults' => [
+ 'description' => 'foo',
+ 'attributes' => [
+ 'mercure' => true,
+ ],
+ ],
+ ],
+ ]);
+
+ $this->assertEquals('foo', $config['defaults']['description']);
+ $this->assertTrue($config['defaults']['attributes']['mercure']);
+ }
}
diff --git a/tests/Metadata/Resource/Factory/DefaultsResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/DefaultsResourceMetadataFactoryTest.php
new file mode 100644
index 00000000000..98b743185d5
--- /dev/null
+++ b/tests/Metadata/Resource/Factory/DefaultsResourceMetadataFactoryTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * 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\Core\Tests\Metadata\Resource\Factory;
+
+use ApiPlatform\Core\Metadata\Resource\Factory\DefaultsResourceMetadataFactory;
+use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
+use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
+use PHPUnit\Framework\TestCase;
+
+class DefaultsResourceMetadataFactoryTest extends TestCase
+{
+ public function testCreate(): void
+ {
+ $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
+ $decoratedProphecy->create(Dummy::class)->shouldBeCalled()->willReturn(new ResourceMetadata(
+ 'foo',
+ null,
+ null,
+ null,
+ null,
+ [
+ 'mercure' => true,
+ ]
+ ));
+
+ $defaults = [
+ 'shortName' => 'bar',
+ 'description' => 'A Foo entity',
+ 'attributes' => [
+ 'mercure' => false,
+ 'messenger' => true,
+ ],
+ ];
+
+ $factory = new DefaultsResourceMetadataFactory($decoratedProphecy->reveal(), $defaults);
+
+ $resourceMetadata = $factory->create(Dummy::class);
+ $this->assertEquals('foo', $resourceMetadata->getShortName());
+ $this->assertEquals('A Foo entity', $resourceMetadata->getDescription());
+ $this->assertTrue($resourceMetadata->getAttribute('mercure'));
+ $this->assertTrue($resourceMetadata->getAttribute('messenger'));
+ }
+}