diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 1f1d74ce2c6..4791c0ce82b 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -14,6 +14,7 @@ $finder = PhpCsFixer\Finder::create() ->in(__DIR__) ->exclude([ + 'src/Bridge/Symfony/Maker/Resources/skeleton', 'tests/Fixtures/app/var', ]) ->notPath('src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php') diff --git a/CHANGELOG.md b/CHANGELOG.md index 67347659e50..7efcd15fa42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Security: **BC** Fix `ApiProperty` `security` attribute expression being passed a class string for the `object` variable on updates/creates - null is now passed instead if the object is not available (#4184) * Security: `ApiProperty` now supports a `security_post_denormalize` attribute, which provides access to the `object` variable for the object being updated/created and `previous_object` for the object before it was updated (#4184) +* Maker: Add `make:data-provider` and `make :data-persister` commands to generate a data provider / persister (#3850) * JSON Schema: Add support for generating property schema with numeric constraint restrictions (#4225) * JSON Schema: Add support for generating property schema with Collection restriction (#4182) * JSON Schema: Add support for generating property schema format for Url and Hostname (#4185) diff --git a/composer.json b/composer.json index 1c0ef10df25..ae80aa92421 100644 --- a/composer.json +++ b/composer.json @@ -72,6 +72,7 @@ "symfony/form": "^3.4 || ^4.4 || ^5.1", "symfony/framework-bundle": "^4.4 || ^5.1", "symfony/http-client": "^4.4 || ^5.1", + "symfony/maker-bundle": "^1.24", "symfony/mercure-bundle": "*", "symfony/messenger": "^4.4 || ^5.1", "symfony/phpunit-bridge": "^5.1.7", @@ -114,7 +115,8 @@ }, "autoload-dev": { "psr-4": { - "ApiPlatform\\Core\\Tests\\": "tests/" + "ApiPlatform\\Core\\Tests\\": "tests/", + "App\\": "tests/Fixtures/app/var/tmp/src/" } }, "config": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 520746c369c..a35e7a951bc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -36,6 +36,8 @@ parameters: - tests/ProphecyTrait.php - tests/Behat/CoverageContext.php - tests/Fixtures/TestBundle/Security/AbstractSecurityUser.php + # Templates for Maker + - src/Bridge/Symfony/Maker/Resources/skeleton earlyTerminatingMethodCalls: PHPUnit\Framework\Constraint\Constraint: - fail diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6c8892bd6b5..c5df9424cdc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,6 +29,7 @@ vendor src/Bridge/NelmioApiDoc src/Bridge/FosUser + src/Bridge/Symfony/Maker/Resources/skeleton .php-cs-fixer.dist.php src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php src/Bridge/Symfony/Bundle/Test/Constraint/ArraySubsetLegacy.php diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index d3f5594c2df..2cdd88f2051 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -127,6 +127,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerElasticsearchConfiguration($container, $config, $loader); $this->registerDataTransformerConfiguration($container); $this->registerSecurityConfiguration($container, $loader); + $this->registerMakerConfiguration($container, $config, $loader); $container->registerForAutoconfiguration(DataPersisterInterface::class) ->addTag('api_platform.data_persister'); @@ -743,6 +744,15 @@ private function registerOpenApiConfiguration(ContainerBuilder $container, array $container->setParameter('api_platform.openapi.license.url', $config['openapi']['license']['url']); } + private function registerMakerConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void + { + if (!$this->isConfigEnabled($container, $config['maker'])) { + return; + } + + $loader->load('maker.xml'); + } + private function buildDeprecationArgs(string $version, string $message): array { return method_exists(Definition::class, 'getDeprecation') diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index d202cd727f1..1b1e2b5a48b 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -26,6 +26,7 @@ use FOS\UserBundle\FOSUserBundle; use GraphQL\GraphQL; use Symfony\Bundle\FullStack; +use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Config\Definition\BaseNode; @@ -209,6 +210,7 @@ public function getConfigTreeBuilder() $this->addMessengerSection($rootNode); $this->addElasticsearchSection($rootNode); $this->addOpenApiSection($rootNode); + $this->addMakerSection($rootNode); $this->addExceptionToStatusSection($rootNode); @@ -629,6 +631,16 @@ private function addDefaultsSection(ArrayNodeDefinition $rootNode): void } } + private function addMakerSection(ArrayNodeDefinition $rootNode): void + { + $rootNode + ->children() + ->arrayNode('maker') + ->{class_exists(MakerBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->end() + ->end(); + } + private function buildDeprecationArgs(string $version, string $message): array { return method_exists(BaseNode::class, 'getDeprecation') diff --git a/src/Bridge/Symfony/Bundle/Resources/config/maker.xml b/src/Bridge/Symfony/Bundle/Resources/config/maker.xml new file mode 100644 index 00000000000..f829cf3af09 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Resources/config/maker.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Bridge/Symfony/Maker/MakeDataPersister.php b/src/Bridge/Symfony/Maker/MakeDataPersister.php new file mode 100644 index 00000000000..0a40200c449 --- /dev/null +++ b/src/Bridge/Symfony/Maker/MakeDataPersister.php @@ -0,0 +1,116 @@ + + * + * 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\Bridge\Symfony\Maker; + +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Question\Question; + +final class MakeDataPersister extends AbstractMaker +{ + private $resourceNameCollection; + + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) + { + $this->resourceNameCollection = $resourceNameCollection; + } + + /** + * {@inheritdoc} + */ + public static function getCommandName(): string + { + return 'make:data-persister'; + } + + /** + * {@inheritdoc} + */ + public static function getCommandDescription(): string + { + return 'Creates an API Platform data persister'; + } + + /** + * {@inheritdoc} + */ + public function configureCommand(Command $command, InputConfiguration $inputConfig) + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data persister (e.g. AwesomeDataPersister)') + ->addArgument('resource-class', InputArgument::OPTIONAL, 'Choose a Resource class') + ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataPersister.txt')); + + $inputConfig->setArgumentAsNonInteractive('resource-class'); + } + + /** + * {@inheritdoc} + */ + public function configureDependencies(DependencyBuilder $dependencies) + { + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command) + { + if (null === $input->getArgument('resource-class')) { + $argument = $command->getDefinition()->getArgument('resource-class'); + + $resourceClasses = $this->resourceNameCollection->create(); + + $question = new Question($argument->getDescription()); + $question->setAutocompleterValues($resourceClasses); + + $value = $io->askQuestion($question); + + $input->setArgument('resource-class', $value); + } + } + + /** + * {@inheritdoc} + */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $dataPersisterClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'DataPersister\\' + ); + $resourceClass = $input->getArgument('resource-class'); + + $generator->generateClass( + $dataPersisterClassNameDetails->getFullName(), + __DIR__.'/Resources/skeleton/DataPersister.tpl.php', + [ + 'resource_class' => null !== $resourceClass ? Str::getShortClassName($resourceClass) : null, + 'resource_full_class_name' => $resourceClass, + ] + ); + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->text([ + 'Next: Open your new data persister class and start customizing it.', + 'Find the documentation at https://api-platform.com/docs/core/data-persisters/', + ]); + } +} diff --git a/src/Bridge/Symfony/Maker/MakeDataProvider.php b/src/Bridge/Symfony/Maker/MakeDataProvider.php new file mode 100644 index 00000000000..3bd380d7579 --- /dev/null +++ b/src/Bridge/Symfony/Maker/MakeDataProvider.php @@ -0,0 +1,122 @@ + + * + * 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\Bridge\Symfony\Maker; + +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\Question; + +class MakeDataProvider extends AbstractMaker +{ + private $resourceNameCollection; + + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) + { + $this->resourceNameCollection = $resourceNameCollection; + } + + /** + * {@inheritdoc} + */ + public static function getCommandName(): string + { + return 'make:data-provider'; + } + + /** + * {@inheritdoc} + */ + public static function getCommandDescription(): string + { + return 'Creates an API Platform data provider'; + } + + /** + * {@inheritdoc} + */ + public function configureCommand(Command $command, InputConfiguration $inputConfig) + { + $command + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data provider (e.g. AwesomeDataProvider)') + ->addArgument('resource-class', InputArgument::OPTIONAL, 'Choose a Resource class') + ->addOption('item-only', null, InputOption::VALUE_NONE, 'Generate only an item data provider') + ->addOption('collection-only', null, InputOption::VALUE_NONE, 'Generate only a collection data provider') + ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataProvider.txt')); + + $inputConfig->setArgumentAsNonInteractive('resource-class'); + } + + /** + * {@inheritdoc} + */ + public function configureDependencies(DependencyBuilder $dependencies) + { + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command) + { + if ($input->getOption('item-only') && $input->getOption('collection-only')) { + throw new RuntimeCommandException('You should at least generate an item or a collection data provider'); + } + + if (null === $input->getArgument('resource-class')) { + $argument = $command->getDefinition()->getArgument('resource-class'); + + $question = new Question($argument->getDescription()); + $question->setAutocompleterValues($this->resourceNameCollection->create()); + + $input->setArgument('resource-class', $io->askQuestion($question)); + } + } + + /** + * {@inheritdoc} + */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $dataProviderClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'DataProvider\\' + ); + $resourceClass = $input->getArgument('resource-class'); + + $generator->generateClass( + $dataProviderClassNameDetails->getFullName(), + __DIR__.'/Resources/skeleton/DataProvider.tpl.php', + [ + 'resource_class' => null !== $resourceClass ? Str::getShortClassName($resourceClass) : null, + 'resource_full_class_name' => $resourceClass, + 'generate_collection' => !$input->getOption('item-only'), + 'generate_item' => !$input->getOption('collection-only'), + ] + ); + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + $io->text([ + 'Next: Open your new data provider class and start customizing it.', + 'Find the documentation at https://api-platform.com/docs/core/data-providers/', + ]); + } +} diff --git a/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt b/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt new file mode 100644 index 00000000000..7afe500d462 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new API Platform data persister class. + +php %command.full_name% AwesomeDataPersister + +If the argument is missing, the command will ask for the class name interactively. diff --git a/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt b/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt new file mode 100644 index 00000000000..73064481560 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new API Platform data provider class. + +php %command.full_name% AwesomeDataProvider + +If the argument is missing, the command will ask for the class name interactively. diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php new file mode 100644 index 00000000000..64b20ef7034 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -0,0 +1,56 @@ + + +namespace ; + +use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; +use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; + +use ; + + +final class implements ContextAwareDataPersisterInterface, ResumableDataPersisterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($data, array $context = []): bool + { + + return $data instanceof ::class; // Add your custom conditions here + + return false; // Add your custom conditions here + + } + + /** + * {@inheritdoc} + */ + public function resumable(array $context = []): bool + { + return false; // Set it to true if you want to call the other data persisters + } + + /** + * {@inheritdoc} + */ + public function persist($data, array $context = [])= 70200) { + echo ': object'; + }?> + + { + // Call your persistence layer to save $data + + return $data; + } + + /** + * {@inheritdoc} + */ + public function remove($data, array $context = []): void + { + // Call your persistence layer to delete $data + } +} diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php new file mode 100644 index 00000000000..b9edcba4021 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php @@ -0,0 +1,63 @@ + + +namespace ; + + +use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; + + +use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; + +use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; + +use ; + + +final class implements RestrictedDataProviderInterface +{ + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + + return ::class === $resourceClass; // Add your custom conditions here + + return false; // Add your custom conditions here + + } + + + /** + * {@inheritdoc} + */ + public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable + { + // Retrieve the collection from somewhere + } + + + + /** + * {@inheritdoc} + */ + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])= 70200) { + echo ': ?object'; + }?> + + { + // Retrieve the item from somewhere then return it or null if not found + } + +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index cf2097fcf46..e5d6de48f87 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -1356,6 +1356,8 @@ private function getBaseContainerBuilderProphecyWithoutDefaultMetadataLoading(ar 'api_platform.json_schema.schema_factory', 'api_platform.listener.view.validate', 'api_platform.listener.view.validate_query_parameters', + 'api_platform.maker.command.data_persister', + 'api_platform.maker.command.data_provider', 'api_platform.mercure.listener.response.add_link_header', 'api_platform.messenger.data_persister', 'api_platform.messenger.data_transformer', diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 71352749b39..acc1c276d88 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -224,6 +224,9 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm 'backward_compatibility_layer' => true, 'swagger_ui_extra_configuration' => [], ], + 'maker' => [ + 'enabled' => true, + ], ], $config); } diff --git a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php new file mode 100644 index 00000000000..8ea178c7403 --- /dev/null +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Filesystem\Filesystem; + +class MakeDataPersisterTest extends KernelTestCase +{ + protected function tearDown(): void + { + (new Filesystem())->remove(self::tempDir()); + } + + /** @dataProvider dataPersisterProvider */ + public function testMakeDataPersister(array $commandInputs, array $userInputs, string $expected) + { + $this->assertFileDoesNotExist(self::tempFile('src/DataPersister/CustomDataPersister.php')); + + $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-persister')); + $tester->setInputs($userInputs); + $tester->execute($commandInputs); + + $this->assertFileExists(self::tempFile('src/DataPersister/CustomDataPersister.php')); + + // Unify line endings + $expected = preg_replace('~\R~u', "\r\n", $expected); + $result = preg_replace('~\R~u', "\r\n", file_get_contents(self::tempFile('src/DataPersister/CustomDataPersister.php'))); + $this->assertSame($expected, $result); + + $display = $tester->getDisplay(); + $this->assertStringContainsString('Success!', $display); + + if (!isset($commandInputs['name'])) { + $this->assertStringContainsString('Choose a class name for your data persister (e.g. AwesomeDataPersister):', $display); + } else { + $this->assertStringNotContainsString('Choose a class name for your data persister (e.g. AwesomeDataPersister):', $display); + } + if (!isset($commandInputs['resource-class'])) { + $this->assertStringContainsString('Choose a Resource class:', $display); + } else { + $this->assertStringNotContainsString('Choose a Resource class:', $display); + } + $this->assertStringContainsString(<< [ + [], + ['CustomDataPersister', ''], + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': object', '', $expected), + ]; + + $expected = <<<'EOF' + [ + [], + ['CustomDataPersister', Question::class], + $expected, + ]; + + yield 'Generate data persister with resource class not interactively' => [ + ['name' => 'CustomDataPersister', 'resource-class' => Question::class], + [], + $expected, + ]; + } + + private static function tempDir(): string + { + return __DIR__.'/../../../Fixtures/app/var/tmp'; + } + + private static function tempFile(string $path): string + { + return sprintf('%s/%s', self::tempDir(), $path); + } +} diff --git a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php new file mode 100644 index 00000000000..921db2e807d --- /dev/null +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Filesystem\Filesystem; + +class MakeDataProviderTest extends KernelTestCase +{ + protected function tearDown(): void + { + (new Filesystem())->remove(self::tempDir()); + } + + /** @dataProvider dataProviderProvider */ + public function testMakeDataProvider(array $commandInputs, array $userInputs, string $expected) + { + $this->assertFileDoesNotExist(self::tempFile('src/DataProvider/CustomDataProvider.php')); + + $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-provider')); + $tester->setInputs($userInputs); + $tester->execute($commandInputs); + + $this->assertFileExists(self::tempFile('src/DataProvider/CustomDataProvider.php')); + + // Unify line endings + $expected = preg_replace('~\R~u', "\r\n", $expected); + $result = preg_replace('~\R~u', "\r\n", file_get_contents(self::tempFile('src/DataProvider/CustomDataProvider.php'))); + $this->assertSame($expected, $result); + + $display = $tester->getDisplay(); + $this->assertStringContainsString('Success!', $display); + + if (!isset($commandInputs['name'])) { + $this->assertStringContainsString('Choose a class name for your data provider (e.g. AwesomeDataProvider):', $display); + } else { + $this->assertStringNotContainsString('Choose a class name for your data provider (e.g. AwesomeDataProvider):', $display); + } + if (!isset($commandInputs['resource-class'])) { + $this->assertStringContainsString(' Choose a Resource class:', $display); + } else { + $this->assertStringNotContainsString('Choose a Resource class:', $display); + } + + $this->assertStringContainsString(<< [ + [], + ['CustomDataProvider', ''], + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), + ]; + + $expected = <<<'EOF' + [ + [], + ['CustomDataProvider', Question::class], + $expected, + ]; + + yield 'Generate all with resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Question::class], + [], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--item-only' => true], + ['CustomDataProvider', ''], + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), + ]; + + $expected = <<<'EOF' + [ + ['--item-only' => true], + ['CustomDataProvider', Question::class], + $expected, + ]; + + yield 'Generate an item data provider with a resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--item-only' => true], + [], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--collection-only' => true], + ['CustomDataProvider', ''], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--collection-only' => true], + ['CustomDataProvider', Question::class], + $expected, + ]; + + yield 'Generate a collection data provider with a resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true], + [], + $expected, + ]; + } + + public function testMakeDataProviderThrows() + { + $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-provider')); + $this->expectException(RuntimeCommandException::class); + $this->expectExceptionMessage('You should at least generate an item or a collection data provider'); + + $tester->execute(['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true, '--item-only' => true]); + } + + private static function tempDir(): string + { + return __DIR__.'/../../../Fixtures/app/var/tmp'; + } + + private static function tempFile(string $path): string + { + return sprintf('%s/%s', self::tempDir(), $path); + } +} diff --git a/tests/Fixtures/app/AppKernel.php b/tests/Fixtures/app/AppKernel.php index 2eff16320d6..0c7ec78a311 100644 --- a/tests/Fixtures/app/AppKernel.php +++ b/tests/Fixtures/app/AppKernel.php @@ -25,6 +25,7 @@ use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Bundle\SecurityBundle\SecurityBundle; use Symfony\Bundle\TwigBundle\TwigBundle; @@ -74,6 +75,7 @@ public function registerBundles(): array new WebProfilerBundle(), new FriendsOfBehatSymfonyExtensionBundle(), new FrameworkBundle(), + new MakerBundle(), ]; if (class_exists(DoctrineMongoDBBundle::class)) {