diff --git a/src/Bridge/Symfony/Bundle/Command/DebugResourceCommand.php b/src/Bridge/Symfony/Bundle/Command/DebugResourceCommand.php
new file mode 100644
index 00000000000..e178a9f39a2
--- /dev/null
+++ b/src/Bridge/Symfony/Bundle/Command/DebugResourceCommand.php
@@ -0,0 +1,121 @@
+
+ *
+ * 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\Bridge\Symfony\Bundle\Command;
+
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\VarDumper\Cloner\ClonerInterface;
+
+final class DebugResourceCommand extends Command
+{
+ protected static $defaultName = 'debug:api-resource';
+
+ private $resourceMetadataCollectionFactory;
+ private $cloner;
+ private $dumper;
+
+ public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ClonerInterface $cloner, $dumper)
+ {
+ parent::__construct();
+ $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
+ $this->cloner = $cloner;
+ $this->dumper = $dumper;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure(): void
+ {
+ $this
+ ->setDescription('Debug API Platform resources')
+ ->addArgument('class', InputArgument::REQUIRED, 'The class you want to debug');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $resourceClass = $input->getArgument('class');
+
+ $resourceCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
+
+ if (0 === \count($resourceCollection)) {
+ $output->writeln(sprintf('No resources found for class %s', $resourceClass));
+
+ return Command::INVALID;
+ }
+
+ $shortName = (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
+
+ $helper = $this->getHelper('question');
+
+ $resources = [];
+ foreach ($resourceCollection as $resource) {
+ if ($resource->getUriTemplate()) {
+ $resources[] = $resource->getUriTemplate();
+ continue;
+ }
+
+ foreach ($resource->getOperations() as $operation) {
+ if ($operation->getUriTemplate()) {
+ $resources[] = $operation->getUriTemplate();
+ break;
+ }
+ }
+ }
+
+ if (\count($resourceCollection) > 1) {
+ $questionResource = new ChoiceQuestion(
+ sprintf('There are %d resources declared on the class %s, which one do you want to debug ? ', \count($resourceCollection), $shortName).\PHP_EOL,
+ $resources
+ );
+
+ $answerResource = $helper->ask($input, $output, $questionResource);
+ $resourceIndex = array_search($answerResource, $resources, true);
+ $selectedResource = $resourceCollection[$resourceIndex];
+ } else {
+ $selectedResource = $resourceCollection[0];
+ $output->writeln(sprintf('Class %s declares 1 resource.', $shortName).\PHP_EOL);
+ }
+
+ $operations = ['Debug the resource itself'];
+ foreach ($selectedResource->getOperations() as $operationName => $operation) {
+ $operations[] = $operationName;
+ }
+
+ $questionOperation = new ChoiceQuestion(
+ sprintf('There are %d operation%s declared on the resource, which one do you want to debug ? ', $selectedResource->getOperations()->count(), $selectedResource->getOperations()->count() > 1 ? 's' : '').\PHP_EOL,
+ $operations
+ );
+
+ $answerOperation = $helper->ask($input, $output, $questionOperation);
+ if ('Debug the resource itself' === $answerOperation) {
+ $this->dumper->dump($this->cloner->cloneVar($selectedResource));
+ $output->writeln('Successfully dumped the selected resource');
+
+ return Command::SUCCESS;
+ }
+
+ $this->dumper->dump($this->cloner->cloneVar($resourceCollection->getOperation($answerOperation)));
+ $output->writeln('Successfully dumped the selected operation');
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml b/src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml
index b1d0571a37f..9686cc415c8 100644
--- a/src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml
+++ b/src/Core/Bridge/Symfony/Bundle/Resources/config/debug.xml
@@ -20,5 +20,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Bridge/Symfony/Bundle/Command/DebugResourceCommandTest.php b/tests/Bridge/Symfony/Bundle/Command/DebugResourceCommandTest.php
new file mode 100644
index 00000000000..4ac34b95a02
--- /dev/null
+++ b/tests/Bridge/Symfony/Bundle/Command/DebugResourceCommandTest.php
@@ -0,0 +1,108 @@
+
+ *
+ * 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\Tests\Bridge\Symfony\Bundle\Command;
+
+use ApiPlatform\Bridge\Symfony\Bundle\Command\DebugResourceCommand;
+use ApiPlatform\Core\Tests\ProphecyTrait;
+use ApiPlatform\Exception\ResourceClassNotFoundException;
+use ApiPlatform\Metadata\Resource\Factory\AttributesResourceMetadataCollectionFactory;
+use PHPUnit\Framework\TestCase;
+use Prophecy\Argument;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Tester\CommandTester;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
+
+class DebugResourceCommandTest extends TestCase
+{
+ use ProphecyTrait;
+
+ private function getCommandTester(DataDumperInterface $dumper = null): CommandTester
+ {
+ $application = new Application();
+ $application->setCatchExceptions(false);
+ $application->setAutoExit(false);
+
+ $application->add(new DebugResourceCommand(new AttributesResourceMetadataCollectionFactory(), new VarCloner(), $dumper ?? new CliDumper()));
+
+ $command = $application->find('debug:api-resource');
+
+ return new CommandTester($command);
+ }
+
+ /**
+ * @requires PHP 8.0
+ */
+ public function testDebugResource()
+ {
+ $varDumper = $this->prophesize(DataDumperInterface::class);
+ $commandTester = $this->getCommandTester($varDumper->reveal());
+ $varDumper->dump(Argument::any())->shouldBeCalledTimes(1);
+ $commandTester->setInputs(['0', '0']);
+ $commandTester->execute([
+ 'class' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\AttributeResource',
+ ]);
+
+ $this->assertStringContainsString('Successfully dumped the selected resource', $commandTester->getDisplay());
+ }
+
+ /**
+ * @requires PHP 8.0
+ */
+ public function testDebugOperation()
+ {
+ $varDumper = $this->prophesize(DataDumperInterface::class);
+ $commandTester = $this->getCommandTester($varDumper->reveal());
+ $varDumper->dump(Argument::any())->shouldBeCalledTimes(1);
+ $commandTester->setInputs(['0', '1']);
+
+ $commandTester->execute([
+ 'class' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\AttributeResource',
+ ]);
+
+ $this->assertStringContainsString('Successfully dumped the selected operation', $commandTester->getDisplay());
+ }
+
+ /**
+ * @requires PHP 8.0
+ */
+ public function testWithOnlyOneResource()
+ {
+ $varDumper = $this->prophesize(DataDumperInterface::class);
+ $commandTester = $this->getCommandTester($varDumper->reveal());
+ $varDumper->dump(Argument::any())->shouldBeCalledTimes(1);
+ $commandTester->setInputs(['1']);
+
+ $commandTester->execute([
+ 'class' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\AlternateResource',
+ ]);
+
+ $this->assertStringContainsString('declares 1 resource', $commandTester->getDisplay());
+ $this->assertStringContainsString('Successfully dumped the selected operation', $commandTester->getDisplay());
+ }
+
+ /**
+ * @requires PHP 8.0
+ */
+ public function testExecuteWithNotExistingClass()
+ {
+ $this->expectException(ResourceClassNotFoundException::class);
+ $commandTester = $this->getCommandTester();
+
+ $commandTester->execute([
+ 'class' => 'ApiPlatform\Tests\Fixtures\TestBundle\Entity\NotExisting',
+ ]);
+ }
+}
diff --git a/tests/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index c9223d6668b..81f1a307e36 100644
--- a/tests/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Core/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -291,6 +291,9 @@ public function testEnableProfilerWithDebug()
$containerBuilderProphecy->setDefinition('debug.api_platform.item_data_provider', Argument::type(Definition::class))->shouldBeCalled();
$containerBuilderProphecy->setDefinition('debug.api_platform.subresource_data_provider', Argument::type(Definition::class))->shouldBeCalled();
$containerBuilderProphecy->setDefinition('debug.api_platform.data_persister', Argument::type(Definition::class))->shouldBeCalled();
+ $containerBuilderProphecy->setDefinition('debug.api_platform.debug_resource.command', Argument::type(Definition::class))->shouldBeCalled();
+ $containerBuilderProphecy->setDefinition('debug.var_dumper.cloner', Argument::type(Definition::class))->shouldBeCalled();
+ $containerBuilderProphecy->setDefinition('debug.var_dumper.cli_dumper', Argument::type(Definition::class))->shouldBeCalled();
$containerBuilder = $containerBuilderProphecy->reveal();
$config = self::DEFAULT_CONFIG;
@@ -794,6 +797,9 @@ public function testKeepCachePoolClearerCacheWarmerWithDebug()
$containerBuilderProphecy->setDefinition('debug.api_platform.item_data_provider', Argument::type(Definition::class))->will(function () {});
$containerBuilderProphecy->setDefinition('debug.api_platform.subresource_data_provider', Argument::type(Definition::class))->will(function () {});
$containerBuilderProphecy->setDefinition('debug.api_platform.data_persister', Argument::type(Definition::class))->will(function () {});
+ $containerBuilderProphecy->setDefinition('debug.api_platform.debug_resource.command', Argument::type(Definition::class))->will(function () {});
+ $containerBuilderProphecy->setDefinition('debug.var_dumper.cloner', Argument::type(Definition::class))->shouldBeCalled();
+ $containerBuilderProphecy->setDefinition('debug.var_dumper.cli_dumper', Argument::type(Definition::class))->shouldBeCalled();
$containerBuilder = $containerBuilderProphecy->reveal();