From 56eb8ce451222e6277c881c3fc2ad50836c2276b Mon Sep 17 00:00:00 2001 From: Anto Date: Thu, 26 Nov 2020 18:19:13 +0100 Subject: [PATCH 1/9] Add maker command to generate data providers/persisters --- composer.json | 1 + .../ApiPlatformExtension.php | 10 +++ .../DependencyInjection/Configuration.php | 12 ++++ .../Symfony/Bundle/Resources/config/maker.xml | 17 +++++ .../Symfony/Maker/MakeDataPersister.php | 65 +++++++++++++++++++ src/Bridge/Symfony/Maker/MakeDataProvider.php | 65 +++++++++++++++++++ .../Resources/help/MakeDataPersister.txt | 5 ++ .../Maker/Resources/help/MakeDataProvider.txt | 5 ++ .../Resources/skeleton/DataPersister.tpl.php | 34 ++++++++++ .../Resources/skeleton/DataProvider.tpl.php | 35 ++++++++++ .../ApiPlatformExtensionTest.php | 3 + .../DependencyInjection/ConfigurationTest.php | 3 + 12 files changed, 255 insertions(+) create mode 100644 src/Bridge/Symfony/Bundle/Resources/config/maker.xml create mode 100644 src/Bridge/Symfony/Maker/MakeDataPersister.php create mode 100644 src/Bridge/Symfony/Maker/MakeDataProvider.php create mode 100644 src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt create mode 100644 src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt create mode 100644 src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php create mode 100644 src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php diff --git a/composer.json b/composer.json index 1c0ef10df25..fdad9c4b6cb 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", 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..75f7d722ce3 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..91303579b2e --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Resources/config/maker.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/src/Bridge/Symfony/Maker/MakeDataPersister.php b/src/Bridge/Symfony/Maker/MakeDataPersister.php new file mode 100644 index 00000000000..8edd4796055 --- /dev/null +++ b/src/Bridge/Symfony/Maker/MakeDataPersister.php @@ -0,0 +1,65 @@ +setDescription('Creates a new class to persists data') + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data persister (e.g. AwesomeDataPersister)') + ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataPersister.txt')) + ; + } + + /** + * {@inheritDoc} + */ + public function configureDependencies(DependencyBuilder $dependencies) + { + } + + /** + * {@inheritDoc} + */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $dataPersisterClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'DataPersister\\' + ); + $generator->generateClass( + $dataPersisterClassNameDetails->getFullName(), + __DIR__.'/Resources/skeleton/DataPersister.tpl.php' + ); + $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/#data-persisters', + ]); + } +} diff --git a/src/Bridge/Symfony/Maker/MakeDataProvider.php b/src/Bridge/Symfony/Maker/MakeDataProvider.php new file mode 100644 index 00000000000..0455e304903 --- /dev/null +++ b/src/Bridge/Symfony/Maker/MakeDataProvider.php @@ -0,0 +1,65 @@ +setDescription('Creates a new class to provide data') + ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data provider (e.g. AwesomeDataProvider)') + ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataProvider.txt')) + ; + } + + /** + * {@inheritDoc} + */ + public function configureDependencies(DependencyBuilder $dependencies) + { + } + + /** + * {@inheritDoc} + */ + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $dataPersisterClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'DataProvider\\' + ); + $generator->generateClass( + $dataPersisterClassNameDetails->getFullName(), + __DIR__.'/Resources/skeleton/DataProvider.tpl.php' + ); + $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/#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..876b9d9000a --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new 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..8b88ee7ce90 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new 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..188f8e58fb6 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -0,0 +1,34 @@ + + +namespace ; + +use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; + +final class implements ContextAwareDataPersisterInterface +{ + /** + * {@inheritdoc} + */ + public function supports($data, array $context = []): bool + { + // return $data instanceof MyEntity; + return false; + } + + /** + * {@inheritdoc} + */ + public function persist($data, array $context = []) + { + // call your persistence layer to save $data + return $data; + } + + /** + * {@inheritdoc} + */ + public function remove($data, array $context = []) + { + // 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..c039b035318 --- /dev/null +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php @@ -0,0 +1,35 @@ + + +namespace ; + +use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; +use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; +use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; + +final class implements ContextAwareCollectionDataProviderInterface, ItemDataProviderInterface, RestrictedDataProviderInterface +{ + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + // return MyEntity::class === $resourceClass; + return false; + } + + /** + * {@inheritdoc} + */ + public function getCollection(string $resourceClass, string $operationName = null, array $context = []) + { + // Retrieve the collection from somewhere + } + + /** + * {@inheritdoc} + */ + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) + { + // 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..1cdb7fdfd37 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -1356,6 +1356,9 @@ 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.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..70732be76e3 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); } From da362fbe3e405f4e829f0ff244239c0364b26f14 Mon Sep 17 00:00:00 2001 From: Anto Date: Fri, 27 Nov 2020 21:24:49 +0100 Subject: [PATCH 2/9] Add new features - Add auto complete on resource class - Options to choose to generate only item/collection data provider --- .php-cs-fixer.dist.php | 1 + phpstan.neon.dist | 2 + .../Symfony/Bundle/Resources/config/maker.xml | 2 + .../Symfony/Maker/MakeDataPersister.php | 64 ++++++++++++--- src/Bridge/Symfony/Maker/MakeDataProvider.php | 81 ++++++++++++++++--- .../Resources/skeleton/DataPersister.tpl.php | 13 ++- .../Resources/skeleton/DataProvider.tpl.php | 30 ++++--- .../DependencyInjection/ConfigurationTest.php | 2 +- 8 files changed, 161 insertions(+), 34 deletions(-) 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/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/src/Bridge/Symfony/Bundle/Resources/config/maker.xml b/src/Bridge/Symfony/Bundle/Resources/config/maker.xml index 91303579b2e..f829cf3af09 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/maker.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/maker.xml @@ -6,10 +6,12 @@ + + diff --git a/src/Bridge/Symfony/Maker/MakeDataPersister.php b/src/Bridge/Symfony/Maker/MakeDataPersister.php index 8edd4796055..476c48469b2 100644 --- a/src/Bridge/Symfony/Maker/MakeDataPersister.php +++ b/src/Bridge/Symfony/Maker/MakeDataPersister.php @@ -1,22 +1,41 @@ + * + * 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; -class MakeDataPersister extends AbstractMaker +final class MakeDataPersister extends AbstractMaker { + private $resourceNameCollection; + + public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollection) + { + $this->resourceNameCollection = $resourceNameCollection; + } + /** - * {@inheritDoc} + * {@inheritdoc} */ public static function getCommandName(): string { @@ -24,26 +43,44 @@ public static function getCommandName(): string } /** - * {@inheritDoc} + * {@inheritdoc} */ public function configureCommand(Command $command, InputConfiguration $inputConfig) { $command - ->setDescription('Creates a new class to persists data') + ->setDescription('Creates an API Platform data persister') ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data persister (e.g. AwesomeDataPersister)') - ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataPersister.txt')) - ; + ->addArgument('resource-class', InputArgument::OPTIONAL, 'Choose a Resource class') + ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataPersister.txt')); + + $inputConfig->setArgumentAsNonInteractive('resource-class'); } /** - * {@inheritDoc} + * {@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} + * {@inheritdoc} */ public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { @@ -51,15 +88,22 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $input->getArgument('name'), 'DataPersister\\' ); + $resourceClass = $input->getArgument('resource-class'); + $generator->generateClass( $dataPersisterClassNameDetails->getFullName(), - __DIR__.'/Resources/skeleton/DataPersister.tpl.php' + __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/#data-persisters', + '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 index 0455e304903..47bd055d7ef 100644 --- a/src/Bridge/Symfony/Maker/MakeDataProvider.php +++ b/src/Bridge/Symfony/Maker/MakeDataProvider.php @@ -1,22 +1,43 @@ + * + * 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} + * {@inheritdoc} */ public static function getCommandName(): string { @@ -24,42 +45,80 @@ public static function getCommandName(): string } /** - * {@inheritDoc} + * {@inheritdoc} */ public function configureCommand(Command $command, InputConfiguration $inputConfig) { $command - ->setDescription('Creates a new class to provide data') + ->setDescription('Creates an API Platform data povider') ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your data provider (e.g. AwesomeDataProvider)') - ->setHelp(file_get_contents(__DIR__.'/Resources/help/MakeDataProvider.txt')) - ; + ->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('name'); + $inputConfig->setArgumentAsNonInteractive('resource-class'); } /** - * {@inheritDoc} + * {@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('name')) { + $argument = $command->getDefinition()->getArgument('name'); + + $question = new Question($argument->getDescription()); + + $input->setArgument('name', $io->askQuestion($question)); + } + + 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} + * {@inheritdoc} */ public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { - $dataPersisterClassNameDetails = $generator->createClassNameDetails( + $dataproviderClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), 'DataProvider\\' ); + $resourceClass = $input->getArgument('resource-class'); + $generator->generateClass( - $dataPersisterClassNameDetails->getFullName(), - __DIR__.'/Resources/skeleton/DataProvider.tpl.php' + $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/#data-providers', + 'Find the documentation at https://api-platform.com/docs/core/data-providers/', ]); } } diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php index 188f8e58fb6..58e19c35d10 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -1,8 +1,11 @@ -namespace ; +namespace ; use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; + +use ; + final class implements ContextAwareDataPersisterInterface { @@ -11,8 +14,11 @@ final class implements ContextAwareDataPersisterInterface */ public function supports($data, array $context = []): bool { - // return $data instanceof MyEntity; - return false; + + return $data instanceof ::class; // Add your custom conditions here + + return false; // Add your custom conditions here + } /** @@ -21,6 +27,7 @@ public function supports($data, array $context = []): bool public function persist($data, array $context = []) { // call your persistence layer to save $data + return $data; } diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php index c039b035318..c1aa0711d23 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php @@ -1,35 +1,47 @@ -namespace ; +namespace ; + use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; + + use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; + use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; + +use ; + -final class implements ContextAwareCollectionDataProviderInterface, ItemDataProviderInterface, RestrictedDataProviderInterface +final class implements RestrictedDataProviderInterface { - /** - * {@inheritdoc} - */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { - // return MyEntity::class === $resourceClass; - return false; + + 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 = []) + 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 = []) + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])= 70200 ? ': ?object' : '') ?> + { // Retrieve the item from somewhere then return it or null if not found } + } diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 70732be76e3..acc1c276d88 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -226,7 +226,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm ], 'maker' => [ 'enabled' => true, - ] + ], ], $config); } From 73eee07d94c2e29f5ee1d74ed5b1fc0acab01623 Mon Sep 17 00:00:00 2001 From: Anto Date: Sat, 28 Nov 2020 09:22:29 +0100 Subject: [PATCH 3/9] Add tests for the new Maker commands --- composer.json | 3 +- src/Bridge/Symfony/Maker/MakeDataProvider.php | 2 +- .../Resources/skeleton/DataPersister.tpl.php | 10 +- .../Resources/skeleton/DataProvider.tpl.php | 9 +- .../Symfony/Maker/MakeDataPersisterTest.php | 167 ++++++++++ .../Symfony/Maker/MakeDataProviderTest.php | 299 ++++++++++++++++++ tests/Fixtures/app/AppKernel.php | 2 + 7 files changed, 486 insertions(+), 6 deletions(-) create mode 100644 tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php create mode 100644 tests/Bridge/Symfony/Maker/MakeDataProviderTest.php diff --git a/composer.json b/composer.json index fdad9c4b6cb..ae80aa92421 100644 --- a/composer.json +++ b/composer.json @@ -115,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/src/Bridge/Symfony/Maker/MakeDataProvider.php b/src/Bridge/Symfony/Maker/MakeDataProvider.php index 47bd055d7ef..030801e5eb9 100644 --- a/src/Bridge/Symfony/Maker/MakeDataProvider.php +++ b/src/Bridge/Symfony/Maker/MakeDataProvider.php @@ -50,7 +50,7 @@ public static function getCommandName(): string public function configureCommand(Command $command, InputConfiguration $inputConfig) { $command - ->setDescription('Creates an API Platform data povider') + ->setDescription('Creates an API Platform data provider') ->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') diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php index 58e19c35d10..207a39f43b7 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -14,7 +14,7 @@ final class implements ContextAwareDataPersisterInterface */ public function supports($data, array $context = []): bool { - + return $data instanceof ::class; // Add your custom conditions here return false; // Add your custom conditions here @@ -24,7 +24,13 @@ public function supports($data, array $context = []): bool /** * {@inheritdoc} */ - public function persist($data, array $context = []) + public function persist($data, array $context = [])= 70200) { + echo ': object'; + }?> + { // call your persistence layer to save $data diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php index c1aa0711d23..2ddaab80594 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php @@ -17,7 +17,7 @@ final class implements + return ::class === $resourceClass; // Add your custom conditions here return false; // Add your custom conditions here @@ -38,7 +38,12 @@ public function getCollection(string $resourceClass, string $operationName = nul /** * {@inheritdoc} */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])= 70200 ? ': ?object' : '') ?> + 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/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php new file mode 100644 index 00000000000..62e6e572c50 --- /dev/null +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -0,0 +1,167 @@ + + * + * 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\Dummy; +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->assertFileNotExists(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')); + + $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->assertSame($expected, file_get_contents(self::tempFile('src/DataPersister/CustomDataPersister.php'))); + + $this->assertStringContainsString('Success!', $display = $tester->getDisplay()); + $this->assertStringContainsString(<< [ + [], + ['CustomDataPersister', ''], + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': object', '', $expected), + ]; + + $expected = <<<'EOF' + [ + [], + ['CustomDataPersister', Dummy::class], + $expected, + ]; + + yield 'Generate data persister with resource class not interactively' => [ + ['name' => 'CustomDataPersister', 'resource-class' => Dummy::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..b06a9edaae6 --- /dev/null +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -0,0 +1,299 @@ + + * + * 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\Dummy; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +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->assertFileNotExists(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')); + + $this->assertSame($expected, file_get_contents(self::tempFile('src/DataProvider/CustomDataProvider.php'))); + + $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', ''], + $expected, + ]; + + $expected = <<<'EOF' + [ + [], + ['CustomDataProvider', Dummy::class], + $expected, + ]; + + yield 'Generate all with resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class], + [], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--item-only' => true], + ['CustomDataProvider', ''], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--item-only' => true], + ['CustomDataProvider', Dummy::class], + $expected, + ]; + + yield 'Generate an item data provider with a resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class, '--item-only' => true], + [], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--collection-only' => true], + ['CustomDataProvider', ''], + $expected, + ]; + + $expected = <<<'EOF' + [ + ['--collection-only' => true], + ['CustomDataProvider', Dummy::class], + $expected, + ]; + + yield 'Generate a collection data provider with a resource class not interactively' => [ + ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class, '--collection-only' => true], + [], + $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/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)) { From 85934bbbff5cd337fb8c16c15db86548a711f103 Mon Sep 17 00:00:00 2001 From: Anto Date: Sat, 28 Nov 2020 09:42:06 +0100 Subject: [PATCH 4/9] Fix tests for PHP 7.1 --- tests/Bridge/Symfony/Maker/MakeDataProviderTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php index b06a9edaae6..c8fdb2a8c72 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -97,7 +97,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null yield 'Generate all without resource class' => [ [], ['CustomDataProvider', ''], - $expected, + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), ]; $expected = <<<'EOF' @@ -176,7 +176,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null yield 'Generate an item data provider without resource class' => [ ['--item-only' => true], ['CustomDataProvider', ''], - $expected, + \PHP_VERSION_ID >= 70200 ? $expected : str_replace(': ?object', '', $expected), ]; $expected = <<<'EOF' @@ -274,6 +274,7 @@ public function getCollection(string $resourceClass, string $operationName = nul } EOF; + yield 'Generate a collection data provider with a resource class' => [ ['--collection-only' => true], ['CustomDataProvider', Dummy::class], From ef4eed9e4611453b25455e6a5921c8e235b0fd35 Mon Sep 17 00:00:00 2001 From: Anto Date: Sat, 28 Nov 2020 09:54:35 +0100 Subject: [PATCH 5/9] Try to fix test on windows --- tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php | 9 +++++---- tests/Bridge/Symfony/Maker/MakeDataProviderTest.php | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php index 62e6e572c50..b0a1be14a26 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -35,6 +35,11 @@ public function testMakeDataPersister(array $commandInputs, array $userInputs, s $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); @@ -48,10 +53,6 @@ public function testMakeDataPersister(array $commandInputs, array $userInputs, s } else { $this->assertStringNotContainsString('Choose a Resource class:', $display); } - - $this->assertSame($expected, file_get_contents(self::tempFile('src/DataPersister/CustomDataPersister.php'))); - - $this->assertStringContainsString('Success!', $display = $tester->getDisplay()); $this->assertStringContainsString(<<assertFileExists(self::tempFile('src/DataProvider/CustomDataProvider.php')); - $this->assertSame($expected, file_get_contents(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); From a01bef280e062a9460b597f522d2b095bd94a78f Mon Sep 17 00:00:00 2001 From: Anto Date: Sat, 28 Nov 2020 11:56:41 +0100 Subject: [PATCH 6/9] Try to fix code coverage generation --- phpunit.xml.dist | 1 + tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php | 2 +- tests/Bridge/Symfony/Maker/MakeDataProviderTest.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) 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/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php index b0a1be14a26..207345c52f6 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -27,7 +27,7 @@ protected function tearDown(): void /** @dataProvider dataPersisterProvider */ public function testMakeDataPersister(array $commandInputs, array $userInputs, string $expected) { - $this->assertFileNotExists(self::tempFile('src/DataPersister/CustomDataPersister.php')); + $this->assertFileDoesNotExist(self::tempFile('src/DataPersister/CustomDataPersister.php')); $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-persister')); $tester->setInputs($userInputs); diff --git a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php index 258b95fa48f..020abe60c91 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -27,7 +27,7 @@ protected function tearDown(): void /** @dataProvider dataProviderProvider */ public function testMakeDataProvider(array $commandInputs, array $userInputs, string $expected) { - $this->assertFileNotExists(self::tempFile('src/DataProvider/CustomDataProvider.php')); + $this->assertFileDoesNotExist(self::tempFile('src/DataProvider/CustomDataProvider.php')); $tester = new CommandTester((new Application(self::bootKernel()))->find('make:data-provider')); $tester->setInputs($userInputs); From 83e36f7851ed81b00a9bbe38ac895b7715e8d31b Mon Sep 17 00:00:00 2001 From: Anto Date: Thu, 3 Dec 2020 05:57:26 +0100 Subject: [PATCH 7/9] Improve coverage and fix review --- .../Maker/Resources/skeleton/DataPersister.tpl.php | 4 ++-- .../Maker/Resources/skeleton/DataProvider.tpl.php | 10 +++++++++- tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php | 4 ++-- tests/Bridge/Symfony/Maker/MakeDataProviderTest.php | 10 ++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php index 207a39f43b7..c8d2653cea5 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -25,7 +25,7 @@ public function supports($data, array $context = []): bool * {@inheritdoc} */ public function persist($data, array $context = [])= 70200) { echo ': object'; @@ -40,7 +40,7 @@ public function persist($data, array $context = []); -final class implements RestrictedDataProviderInterface +final class implements RestrictedDataProviderInterface { public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { diff --git a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php index 207345c52f6..da4327f4a53 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -92,7 +92,7 @@ public function persist($data, array $context = []): object /** * {@inheritdoc} */ - public function remove($data, array $context = []) + public function remove($data, array $context = []): void { // call your persistence layer to delete $data } @@ -136,7 +136,7 @@ public function persist($data, array $context = []): Dummy /** * {@inheritdoc} */ - public function remove($data, array $context = []) + public function remove($data, array $context = []): void { // call your persistence layer to delete $data } diff --git a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php index 020abe60c91..6d132b083d0 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -14,6 +14,7 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; 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; @@ -291,6 +292,15 @@ public function getCollection(string $resourceClass, string $operationName = nul ]; } + 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' => Dummy::class, '--collection-only' => true, '--item-only' => true]); + } + private static function tempDir(): string { return __DIR__.'/../../../Fixtures/app/var/tmp'; From ad1fb343631b850eb0b29ed5d60b1c912b3e971a Mon Sep 17 00:00:00 2001 From: Anto Date: Mon, 7 Dec 2020 18:56:44 +0100 Subject: [PATCH 8/9] Fix indentation --- .../Symfony/Maker/Resources/skeleton/DataPersister.tpl.php | 4 ++-- tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php index c8d2653cea5..d4c4967f512 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -15,9 +15,9 @@ final class implements ContextAwareDataPersisterInterface public function supports($data, array $context = []): bool { - return $data instanceof ::class; // Add your custom conditions here + return $data instanceof ::class; // Add your custom conditions here - return false; // Add your custom conditions here + return false; // Add your custom conditions here } diff --git a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php index da4327f4a53..f972b5035c3 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataPersisterTest.php @@ -76,7 +76,7 @@ final class CustomDataPersister implements ContextAwareDataPersisterInterface */ public function supports($data, array $context = []): bool { - return false; // Add your custom conditions here + return false; // Add your custom conditions here } /** @@ -120,7 +120,7 @@ final class CustomDataPersister implements ContextAwareDataPersisterInterface */ public function supports($data, array $context = []): bool { - return $data instanceof Dummy::class; // Add your custom conditions here + return $data instanceof Dummy::class; // Add your custom conditions here } /** From 52c0b80e8c7547df3db038b8045af2ec285c0f64 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Fri, 18 Jun 2021 12:14:41 +0200 Subject: [PATCH 9/9] fix: minor changes --- CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 6 +- .../Symfony/Maker/MakeDataPersister.php | 9 +- src/Bridge/Symfony/Maker/MakeDataProvider.php | 22 +++-- .../Resources/help/MakeDataPersister.txt | 2 +- .../Maker/Resources/help/MakeDataProvider.txt | 2 +- .../Resources/skeleton/DataPersister.tpl.php | 27 ++++-- .../Resources/skeleton/DataProvider.tpl.php | 11 ++- .../ApiPlatformExtensionTest.php | 1 - .../Symfony/Maker/MakeDataPersisterTest.php | 66 +++++++++------ .../Symfony/Maker/MakeDataProviderTest.php | 82 +++++++++++-------- 11 files changed, 141 insertions(+), 88 deletions(-) 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/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index 75f7d722ce3..1b1e2b5a48b 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -635,9 +635,9 @@ private function addMakerSection(ArrayNodeDefinition $rootNode): void { $rootNode ->children() - ->arrayNode('maker') - ->{class_exists(MakerBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}() - ->end() + ->arrayNode('maker') + ->{class_exists(MakerBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->end() ->end(); } diff --git a/src/Bridge/Symfony/Maker/MakeDataPersister.php b/src/Bridge/Symfony/Maker/MakeDataPersister.php index 476c48469b2..0a40200c449 100644 --- a/src/Bridge/Symfony/Maker/MakeDataPersister.php +++ b/src/Bridge/Symfony/Maker/MakeDataPersister.php @@ -42,13 +42,20 @@ 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 - ->setDescription('Creates an API Platform data persister') ->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')); diff --git a/src/Bridge/Symfony/Maker/MakeDataProvider.php b/src/Bridge/Symfony/Maker/MakeDataProvider.php index 030801e5eb9..3bd380d7579 100644 --- a/src/Bridge/Symfony/Maker/MakeDataProvider.php +++ b/src/Bridge/Symfony/Maker/MakeDataProvider.php @@ -44,20 +44,26 @@ 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 - ->setDescription('Creates an API Platform data provider') ->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('name'); $inputConfig->setArgumentAsNonInteractive('resource-class'); } @@ -74,14 +80,6 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma throw new RuntimeCommandException('You should at least generate an item or a collection data provider'); } - if (null === $input->getArgument('name')) { - $argument = $command->getDefinition()->getArgument('name'); - - $question = new Question($argument->getDescription()); - - $input->setArgument('name', $io->askQuestion($question)); - } - if (null === $input->getArgument('resource-class')) { $argument = $command->getDefinition()->getArgument('resource-class'); @@ -97,14 +95,14 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma */ public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { - $dataproviderClassNameDetails = $generator->createClassNameDetails( + $dataProviderClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), 'DataProvider\\' ); $resourceClass = $input->getArgument('resource-class'); $generator->generateClass( - $dataproviderClassNameDetails->getFullName(), + $dataProviderClassNameDetails->getFullName(), __DIR__.'/Resources/skeleton/DataProvider.tpl.php', [ 'resource_class' => null !== $resourceClass ? Str::getShortClassName($resourceClass) : null, diff --git a/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt b/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt index 876b9d9000a..7afe500d462 100644 --- a/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataPersister.txt @@ -1,4 +1,4 @@ -The %command.name% command generates a new data persister class. +The %command.name% command generates a new API Platform data persister class. php %command.full_name% AwesomeDataPersister diff --git a/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt b/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt index 8b88ee7ce90..73064481560 100644 --- a/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt +++ b/src/Bridge/Symfony/Maker/Resources/help/MakeDataProvider.txt @@ -1,4 +1,4 @@ -The %command.name% command generates a new data provider class. +The %command.name% command generates a new API Platform data provider class. php %command.full_name% AwesomeDataProvider diff --git a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php index d4c4967f512..64b20ef7034 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataPersister.tpl.php @@ -3,15 +3,16 @@ namespace ; use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; +use ApiPlatform\Core\DataPersister\ResumableDataPersisterInterface; use ; -final class implements ContextAwareDataPersisterInterface +final class implements ContextAwareDataPersisterInterface, ResumableDataPersisterInterface { /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function supports($data, array $context = []): bool { @@ -22,8 +23,16 @@ public function supports($data, array $context = []): bool } /** - * {@inheritdoc} - */ + * {@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 = []) { - // call your persistence layer to save $data + // Call your persistence layer to save $data return $data; } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function remove($data, array $context = []): void { - // call your persistence layer to delete $data + // 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 index fe4ce15a930..b9edcba4021 100644 --- a/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php +++ b/src/Bridge/Symfony/Maker/Resources/skeleton/DataProvider.tpl.php @@ -23,6 +23,9 @@ final class implements RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { @@ -34,8 +37,8 @@ public function supports(string $resourceClass, string $operationName = null, ar /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable { // Retrieve the collection from somewhere @@ -44,8 +47,8 @@ public function getCollection(string $resourceClass, string $operationName = nul /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) [ [], - ['CustomDataPersister', Dummy::class], + ['CustomDataPersister', Question::class], $expected, ]; yield 'Generate data persister with resource class not interactively' => [ - ['name' => 'CustomDataPersister', 'resource-class' => Dummy::class], + ['name' => 'CustomDataPersister', 'resource-class' => Question::class], [], $expected, ]; diff --git a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php index 6d132b083d0..921db2e807d 100644 --- a/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php +++ b/tests/Bridge/Symfony/Maker/MakeDataProviderTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +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; @@ -75,22 +75,25 @@ public function dataProviderProvider(): Generator final class CustomDataProvider implements ContextAwareCollectionDataProviderInterface, ItemDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return false; // Add your custom conditions here } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable { // Retrieve the collection from somewhere } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve the item from somewhere then return it or null if not found @@ -112,27 +115,30 @@ public function getItem(string $resourceClass, $id, string $operationName = null use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; final class CustomDataProvider implements ContextAwareCollectionDataProviderInterface, ItemDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { - return Dummy::class === $resourceClass; // Add your custom conditions here + return Question::class === $resourceClass; // Add your custom conditions here } /** - * {@inheritdoc} - */ + * {@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 = []): ?Dummy + * {@inheritdoc} + */ + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Question { // Retrieve the item from somewhere then return it or null if not found } @@ -142,12 +148,12 @@ public function getItem(string $resourceClass, $id, string $operationName = null yield 'Generate all with resource class' => [ [], - ['CustomDataProvider', Dummy::class], + ['CustomDataProvider', Question::class], $expected, ]; yield 'Generate all with resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class], + ['name' => 'CustomDataProvider', 'resource-class' => Question::class], [], $expected, ]; @@ -162,14 +168,17 @@ public function getItem(string $resourceClass, $id, string $operationName = null final class CustomDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return false; // Add your custom conditions here } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?object { // Retrieve the item from somewhere then return it or null if not found @@ -190,19 +199,22 @@ public function getItem(string $resourceClass, $id, string $operationName = null use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; final class CustomDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { - return Dummy::class === $resourceClass; // Add your custom conditions here + return Question::class === $resourceClass; // Add your custom conditions here } /** - * {@inheritdoc} - */ - public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Dummy + * {@inheritdoc} + */ + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Question { // Retrieve the item from somewhere then return it or null if not found } @@ -211,12 +223,12 @@ public function getItem(string $resourceClass, $id, string $operationName = null EOF; yield 'Generate an item data provider with a resource class' => [ ['--item-only' => true], - ['CustomDataProvider', Dummy::class], + ['CustomDataProvider', Question::class], $expected, ]; yield 'Generate an item data provider with a resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class, '--item-only' => true], + ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--item-only' => true], [], $expected, ]; @@ -231,14 +243,17 @@ public function getItem(string $resourceClass, $id, string $operationName = null final class CustomDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { return false; // Add your custom conditions here } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable { // Retrieve the collection from somewhere @@ -259,18 +274,21 @@ public function getCollection(string $resourceClass, string $operationName = nul use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface; use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; -use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question; final class CustomDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface { + /** + * {@inheritdoc} + */ public function supports(string $resourceClass, string $operationName = null, array $context = []): bool { - return Dummy::class === $resourceClass; // Add your custom conditions here + return Question::class === $resourceClass; // Add your custom conditions here } /** - * {@inheritdoc} - */ + * {@inheritdoc} + */ public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable { // Retrieve the collection from somewhere @@ -281,12 +299,12 @@ public function getCollection(string $resourceClass, string $operationName = nul yield 'Generate a collection data provider with a resource class' => [ ['--collection-only' => true], - ['CustomDataProvider', Dummy::class], + ['CustomDataProvider', Question::class], $expected, ]; yield 'Generate a collection data provider with a resource class not interactively' => [ - ['name' => 'CustomDataProvider', 'resource-class' => Dummy::class, '--collection-only' => true], + ['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true], [], $expected, ]; @@ -298,7 +316,7 @@ public function testMakeDataProviderThrows() $this->expectException(RuntimeCommandException::class); $this->expectExceptionMessage('You should at least generate an item or a collection data provider'); - $tester->execute(['name' => 'CustomDataProvider', 'resource-class' => Dummy::class, '--collection-only' => true, '--item-only' => true]); + $tester->execute(['name' => 'CustomDataProvider', 'resource-class' => Question::class, '--collection-only' => true, '--item-only' => true]); } private static function tempDir(): string