diff --git a/src/Symfony/Bundle/ApiPlatformBundle.php b/src/Symfony/Bundle/ApiPlatformBundle.php index f7fc270f085..b149a7720d9 100644 --- a/src/Symfony/Bundle/ApiPlatformBundle.php +++ b/src/Symfony/Bundle/ApiPlatformBundle.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Symfony\Bundle; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AttributeFilterPass; +use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AttributeResourcePass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass; @@ -47,6 +48,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new DataProviderPass()); // Run the compiler pass before the {@see ResolveInstanceofConditionalsPass} to allow autoconfiguration of generated filter definitions. $container->addCompilerPass(new AttributeFilterPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 101); + $container->addCompilerPass(new AttributeResourcePass()); $container->addCompilerPass(new FilterPass()); $container->addCompilerPass(new ElasticsearchClientPass()); $container->addCompilerPass(new GraphQlTypePass()); diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index 4e273866bdb..3279336ed86 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -49,6 +49,7 @@ use Ramsey\Uuid\Uuid; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -169,6 +170,11 @@ public function load(array $configs, ContainerBuilder $container): void ->addTag('api_platform.uri_variables.transformer'); $container->registerForAutoconfiguration(ParameterProviderInterface::class) ->addTag('api_platform.parameter_provider'); + $container->registerAttributeForAutoconfiguration(ApiResource::class, static function (ChildDefinition $definition): void { + $definition->setAbstract(true) + ->addTag('api_platform.resource') + ->addTag('container.excluded', ['source' => 'by #[ApiResource] attribute']); + }); if (!$container->has('api_platform.state.item_provider')) { $container->setAlias('api_platform.state.item_provider', 'api_platform.state_provider.object'); diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php new file mode 100644 index 00000000000..a81dba929bc --- /dev/null +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php @@ -0,0 +1,45 @@ + + * + * 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\Symfony\Bundle\DependencyInjection\Compiler; + +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Registers resource classes from {@see ApiResource} attribute. + * + * @internal + * + * @author Jérôme Tamarelle + */ +final class AttributeResourcePass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container): void + { + $classes = $container->getParameter('api_platform.class_name_resources'); + + // findTaggedServiceIds cannot be used, as the services are excluded + foreach ($container->getDefinitions() as $definition) { + if ($definition->hasTag('api_platform.resource')) { + $classes[] = $definition->getClass(); + } + } + + $container->setParameter('api_platform.class_name_resources', array_unique($classes)); + } +} diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 9460dfcbce8..6dc5b9d3974 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -141,6 +141,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->arrayNode('resource_class_directories') ->prototype('scalar')->end() + ->setDeprecated('api-platform/symfony', '4.1', 'The "resource_class_directories" configuration is deprecated, classes using #[ApiResource] attribute are autoconfigured by the dependency injection container.') ->end() ->arrayNode('serializer') ->addDefaultsIfNotSet() diff --git a/tests/Symfony/Bundle/ApiPlatformBundleTest.php b/tests/Symfony/Bundle/ApiPlatformBundleTest.php index d6b1643e571..ffad7c49375 100644 --- a/tests/Symfony/Bundle/ApiPlatformBundleTest.php +++ b/tests/Symfony/Bundle/ApiPlatformBundleTest.php @@ -15,6 +15,7 @@ use ApiPlatform\Symfony\Bundle\ApiPlatformBundle; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AttributeFilterPass; +use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AttributeResourcePass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\AuthenticatorManagerPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\DataProviderPass; use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ElasticsearchClientPass; @@ -45,6 +46,7 @@ public function testBuild(): void $containerProphecy = $this->prophesize(ContainerBuilder::class); $containerProphecy->addCompilerPass(Argument::type(DataProviderPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(AttributeFilterPass::class), PassConfig::TYPE_BEFORE_OPTIMIZATION, 101)->willReturn($containerProphecy->reveal())->shouldBeCalled(); + $containerProphecy->addCompilerPass(Argument::type(AttributeResourcePass::class))->shouldBeCalled()->willReturn($containerProphecy->reveal())->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(FilterPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(ElasticsearchClientPass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled(); $containerProphecy->addCompilerPass(Argument::type(GraphQlTypePass::class))->willReturn($containerProphecy->reveal())->shouldBeCalled();