From b15ee770af6b0b8f5437ca3195d90828a94315ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 5 Feb 2025 11:13:08 +0100 Subject: [PATCH 1/3] feat(symfony): Autoconfigure resource dirs using ApiResource attribute --- src/Symfony/Bundle/ApiPlatformBundle.php | 2 + .../ApiPlatformExtension.php | 5 ++ .../Compiler/AttributeResourcePass.php | 48 +++++++++++++++++++ .../Symfony/Bundle/ApiPlatformBundleTest.php | 2 + 4 files changed, 57 insertions(+) create mode 100644 src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php 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..b87b28b092a 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,10 @@ 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->addTag('api_platform.resource'); + $definition->addTag('container.excluded', ['source' => __FILE__]); + }); 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..9a4a8d2b455 --- /dev/null +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php @@ -0,0 +1,48 @@ + + * + * 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 + { + $resourceClassDirectories = $container->getParameter('api_platform.resource_class_directories'); + + // findTaggedServiceIds cannot be used, as the services are excluded + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->hasTag('api_platform.resource')) { + $r = new \ReflectionClass($definition->getClass()); + if ($r->getFileName()) { + $resourceClassDirectories[] = \dirname($r->getFileName()); + } + } + } + $resourceClassDirectories = array_unique($resourceClassDirectories); + $container->setParameter('api_platform.resource_class_directories', $resourceClassDirectories); + } +} 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(); From 2f49ba9a30538db1a4d5adcf9844ffd58a1b593f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 5 Feb 2025 13:32:01 +0100 Subject: [PATCH 2/3] Override api_platform.class_name_resources instead of api_platform.resource_class_directories --- .../Compiler/AttributeResourcePass.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php index 9a4a8d2b455..a81dba929bc 100644 --- a/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php +++ b/src/Symfony/Bundle/DependencyInjection/Compiler/AttributeResourcePass.php @@ -31,18 +31,15 @@ final class AttributeResourcePass implements CompilerPassInterface */ public function process(ContainerBuilder $container): void { - $resourceClassDirectories = $container->getParameter('api_platform.resource_class_directories'); + $classes = $container->getParameter('api_platform.class_name_resources'); // findTaggedServiceIds cannot be used, as the services are excluded - foreach ($container->getDefinitions() as $id => $definition) { + foreach ($container->getDefinitions() as $definition) { if ($definition->hasTag('api_platform.resource')) { - $r = new \ReflectionClass($definition->getClass()); - if ($r->getFileName()) { - $resourceClassDirectories[] = \dirname($r->getFileName()); - } + $classes[] = $definition->getClass(); } } - $resourceClassDirectories = array_unique($resourceClassDirectories); - $container->setParameter('api_platform.resource_class_directories', $resourceClassDirectories); + + $container->setParameter('api_platform.class_name_resources', array_unique($classes)); } } From d2a7a55551b5cabdd05c8ce1d64708e91760859e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 7 Feb 2025 00:34:54 +0100 Subject: [PATCH 3/3] feat(symfony): Deprecate resource_class_directories config --- .../Bundle/DependencyInjection/ApiPlatformExtension.php | 5 +++-- src/Symfony/Bundle/DependencyInjection/Configuration.php | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index b87b28b092a..3279336ed86 100644 --- a/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -171,8 +171,9 @@ public function load(array $configs, ContainerBuilder $container): void $container->registerForAutoconfiguration(ParameterProviderInterface::class) ->addTag('api_platform.parameter_provider'); $container->registerAttributeForAutoconfiguration(ApiResource::class, static function (ChildDefinition $definition): void { - $definition->addTag('api_platform.resource'); - $definition->addTag('container.excluded', ['source' => __FILE__]); + $definition->setAbstract(true) + ->addTag('api_platform.resource') + ->addTag('container.excluded', ['source' => 'by #[ApiResource] attribute']); }); if (!$container->has('api_platform.state.item_provider')) { 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()