diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index cf39090b833..97d00587f79 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -97,6 +97,7 @@ public function load(array $configs, ContainerBuilder $container): void } $this->registerCommonConfiguration($container, $config, $loader, $formats, $patchFormats, $errorFormats); + $this->registerRestListenerConfiguration($container, $config, $loader); $this->registerMetadataConfiguration($container, $config, $loader); $this->registerOAuthConfiguration($container, $config); $this->registerSwaggerConfiguration($container, $config, $loader); @@ -370,6 +371,18 @@ private function registerJsonProblemConfiguration(array $errorFormats, XmlFileLo $loader->load('problem.xml'); } + private function registerRestListenerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void + { + $container->setParameter('api_platform.rest.disable_rest', $config['disable_rest']); + + if ($config['disable_rest']) { + return; + } + + $loader->load('rest_event_listener.xml'); + + } + private function registerGraphQlConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void { $enabled = $this->isConfigEnabled($container, $config['graphql']); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index b49c43b28ed..287c951cf35 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -106,6 +106,7 @@ public function getConfigTreeBuilder() ->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end() ->end() ->end() + ->booleanNode('disable_rest')->defaultFalse()->info('Disable the rest listeners')->end() ->booleanNode('enable_fos_user')->defaultValue(class_exists(FOSUserBundle::class))->info('Enable the FOSUserBundle integration.')->end() ->booleanNode('enable_nelmio_api_doc') ->defaultFalse() diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 5b07b360cf2..4cfa2c3f457 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -44,6 +44,7 @@ %api_platform.enable_docs% %api_platform.graphql.graphiql.enabled% %api_platform.graphql.graphql_playground.enabled% + %api_platform.rest.disable_rest% @@ -155,56 +156,6 @@ - - - - - %api_platform.formats% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %api_platform.error_formats% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml index 1a50a9b0e14..cebe44f8211 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/metadata/metadata.xml @@ -25,6 +25,7 @@ %api_platform.patch_formats% + %api_platform.rest.disable_rest%% diff --git a/src/Bridge/Symfony/Bundle/Resources/config/rest_event_listener.xml b/src/Bridge/Symfony/Bundle/Resources/config/rest_event_listener.xml new file mode 100644 index 00000000000..6447def5ba7 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Resources/config/rest_event_listener.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php index ef2590edad1..9793d920668 100644 --- a/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php +++ b/src/Metadata/Resource/Factory/OperationResourceMetadataFactory.php @@ -43,11 +43,13 @@ final class OperationResourceMetadataFactory implements ResourceMetadataFactoryI private $decorated; private $patchFormats; + private $disableRest; - public function __construct(ResourceMetadataFactoryInterface $decorated, array $patchFormats = []) + public function __construct(ResourceMetadataFactoryInterface $decorated, array $patchFormats = [], $disableRest = false) { $this->decorated = $decorated; $this->patchFormats = $patchFormats; + $this->disableRest = $disableRest; } /** @@ -59,6 +61,11 @@ public function create(string $resourceClass): ResourceMetadata $isAbstract = (new \ReflectionClass($resourceClass))->isAbstract(); $collectionOperations = $resourceMetadata->getCollectionOperations(); + // If rest is disabled we don't want to add the default operations + // but we want to have the placeholder get operation + if (true === $this->disableRest) { + $collectionOperations = []; + } if (null === $collectionOperations) { $resourceMetadata = $resourceMetadata->withCollectionOperations($this->createOperations($isAbstract ? ['GET'] : ['GET', 'POST'])); } else { @@ -66,6 +73,12 @@ public function create(string $resourceClass): ResourceMetadata } $itemOperations = $resourceMetadata->getItemOperations(); + // If rest is disabled we don't want to add the default operations + // but we want to have the placeholder get operation + if (true === $this->disableRest) { + $itemOperations = []; + } + if (null === $itemOperations) { $methods = ['GET', 'DELETE']; diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 11f23d41b3f..514700bb8b6 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -294,7 +294,7 @@ public function testFosUserPriority() $builder = new ContainerBuilder(); $loader = new XmlFileLoader($builder, new FileLocator(\dirname(__DIR__).'/../../../../src/Bridge/Symfony/Bundle/Resources/config')); - $loader->load('api.xml'); + $loader->load('rest_event_listener.xml'); $loader->load('fos_user.xml'); $fosListener = $builder->getDefinition('api_platform.fos_user.event_listener'); @@ -757,6 +757,26 @@ public function testKeepCachePoolClearerCacheWarmerWithDebug() $this->extension->load(self::DEFAULT_CONFIG, $containerBuilder); } + public function testDisableRest() + { + $containerBuilderProphecy = $this->getBaseContainerBuilderProphecy(); + $containerBuilderProphecy->setDefinition('api_platform.listener.request.add_format', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setDefinition('api_platform.listener.request.read', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setDefinition('api_platform.listener.view.write', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setDefinition('api_platform.listener.request.deserialize', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setDefinition('api_platform.listener.view.serialize', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setDefinition('api_platform.listener.view.respond', Argument::type(Definition::class))->shouldNotBeCalled(); + $containerBuilderProphecy->setParameter('api_platform.rest.disable_rest', false)->shouldNotBeCalled(); + $containerBuilderProphecy->setParameter('api_platform.rest.disable_rest', true)->shouldBeCalled(); + + $containerBuilder = $containerBuilderProphecy->reveal(); + + $config = self::DEFAULT_CONFIG; + $config['api_platform']['disable_rest'] = true; + + $this->extension->load($config, $containerBuilder); + } + private function getPartialContainerBuilderProphecy() { $parameterBag = new EnvPlaceholderParameterBag(); @@ -806,6 +826,7 @@ private function getPartialContainerBuilderProphecy() 'api_platform.http_cache.public' => null, 'api_platform.enable_entrypoint' => true, 'api_platform.enable_docs' => true, + 'api_platform.rest.disable_rest' => false, ]; $pagination = [ @@ -1073,6 +1094,7 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo 'api_platform.graphql.collection.pagination' => ['enabled' => true], 'api_platform.graphql.graphiql.enabled' => true, 'api_platform.graphql.graphql_playground.enabled' => true, + 'api_platform.rest.disable_rest' => false, 'api_platform.resource_class_directories' => Argument::type('array'), 'api_platform.validator.serialize_payload_fields' => [], 'api_platform.elasticsearch.enabled' => false, diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index a891c8724e2..20c44a5cd29 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -204,6 +204,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm ], 'allow_plain_identifiers' => false, 'resource_class_directories' => [], + 'disable_rest' => false ], $config); } diff --git a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php index 4bdbbbdecb2..413fcb060d4 100644 --- a/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php +++ b/tests/Metadata/Resource/Factory/OperationResourceMetadataFactoryTest.php @@ -29,12 +29,12 @@ class OperationResourceMetadataFactoryTest extends TestCase /** * @dataProvider getMetadata */ - public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $after, array $formats = []): void + public function testCreateOperation(ResourceMetadata $before, ResourceMetadata $after, array $formats = [], $disableRest = false ): void { $decoratedProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $decoratedProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($before); - $this->assertEquals($after, (new OperationResourceMetadataFactory($decoratedProphecy->reveal(), $formats))->create(Dummy::class)); + $this->assertEquals($after, (new OperationResourceMetadataFactory($decoratedProphecy->reveal(), $formats, $disableRest))->create(Dummy::class)); } public function getMetadata(): iterable @@ -46,6 +46,7 @@ public function getMetadata(): iterable yield [new ResourceMetadata(null, null, null, null, [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get', 'put', 'patch', 'delete']), [], null, [], []), $jsonapi]; yield [new ResourceMetadata(null, null, null, ['get'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['get']), [], null, [], [])]; yield [new ResourceMetadata(null, null, null, [], [], null, [], []), new ResourceMetadata(null, null, null, $this->getPlaceholderOperation(), [], null, [], [])]; + yield [new ResourceMetadata(null, null, null, null, null, null, [], []), new ResourceMetadata(null, null, null, $this->getPlaceholderOperation(), [], null, [], []), [], true]; yield [new ResourceMetadata(null, null, null, ['put'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['put']), [], null, [], [])]; yield [new ResourceMetadata(null, null, null, ['delete'], [], null, [], []), new ResourceMetadata(null, null, null, $this->getOperations(['delete']), [], null, [], [])]; yield [new ResourceMetadata(null, null, null, ['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], [], null, [], []), new ResourceMetadata(null, null, null, array_merge(['patch' => ['method' => 'PATCH', 'route_name' => 'patch']], $this->getPlaceholderOperation()), [], null, [], [])];