diff --git a/src/Maker/MakeDataPersister.php b/src/Maker/MakeDataPersister.php new file mode 100644 index 000000000..2e730f04c --- /dev/null +++ b/src/Maker/MakeDataPersister.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Maker; + +use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; +use Symfony\Bundle\MakerBundle\ConsoleStyle; +use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper; +use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; +use Symfony\Bundle\MakerBundle\FileManager; +use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; +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\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * @author Imad ZAIRIG + * + * @internal + */ +final class MakeDataPersister extends AbstractMaker +{ + /** @var ResourceNameCollectionFactoryInterface */ + protected $ressourceNameCollectionFactory; + /** @var ResourceMetadataFactoryInterface */ + protected $ressourceMetaDataFactory; + protected $fileManager; + protected $doctrineHelper; + protected $resourcesClassNames = []; + + public function __construct( + FileManager $fileManager, + DoctrineHelper $doctrineHelper, + $ressourceNameCollectionFactory = null, + $ressourceMetaDataFactory = null + ) { + $this->fileManager = $fileManager; + $this->doctrineHelper = $doctrineHelper; + $this->ressourceNameCollection = $ressourceNameCollectionFactory; + $this->ressourceMetaDataFactory = $ressourceMetaDataFactory; + } + + public static function getCommandName(): string + { + return 'make:api:data-persister'; + } + + public function configureCommand(Command $command, InputConfiguration $inputConfig) + { + $command + ->setDescription('Creates a API Platform Data Persister') + ->addArgument('name', InputArgument::OPTIONAL, 'The name of the Data Persister class (e.g. CustomDataPersister)') + ->addArgument('resource', InputArgument::OPTIONAL, 'The name of the resource class') + ->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeDataPersister.txt')); + $inputConfig->setArgumentAsNonInteractive('resource'); + } + + public function interact(InputInterface $input, ConsoleStyle $io, Command $command) + { + if (null === $input->getArgument('resource')) { + $argument = $command->getDefinition()->getArgument('resource'); + $question = $this->createResourceClassQuestion($argument->getDescription()); + $value = $io->askQuestion($question); + $input->setArgument('resource', $value); + $doctrineOption = new InputOption('is_doctrine_persister', 'a', InputOption::VALUE_NONE, 'Would you like your persister to call the core Doctrine persister?'); + $command->getDefinition()->addOption($doctrineOption); + $this->resourcesClassNames = array_flip($this->getResources()); + + if (\in_array($value, $this->getResources()) && $this->doctrineHelper->isClassAMappedEntity($this->resourcesClassNames[$value])) { + $description = $command->getDefinition()->getOption('is_doctrine_persister')->getDescription(); + $question = new ConfirmationQuestion($description, false); + $value = $io->askQuestion($question); + + $input->setOption('is_doctrine_persister', $value); + } + } + } + + public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) + { + $templateVariables = []; + $resourceShortName = $input->getArgument('resource'); + $this->resourcesClassNames = array_flip($this->getResources()); + + if ($resourceShortName && \in_array($resourceShortName, $this->resourcesClassNames)) { + $resourceClasseName = $this->resourcesClassNames[$resourceShortName]; + $templateVariables['resource_short_name'] = $resourceShortName; + $templateVariables['resource_class_name'] = $resourceClasseName; + } + + $dataPersisterClassNameDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'DataPersister\\', + 'DataPersister' + ); + + if ($input->getOption('is_doctrine_persister')) { + $templateVariables['is_doctrine_persister'] = true; + if (!$this->fileManager->fileExists($path = 'config/services.yaml')) { + throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. This command requires that file to exist so that it can be updated.'); + } + $manipulator = new YamlSourceManipulator($this->fileManager->getFileContents($path)); + + $servicesData = $manipulator->getData(); + $servicesData['services'] = [$dataPersisterClassNameDetails->getFullName() => ['decorates' => 'api_platform.doctrine.orm.data_persister']] + $servicesData['services']; + $manipulator->setData($servicesData); + $this->fileManager->dumpFile($path, $manipulator->getContents()); + } + + $generator->generateClass( + $dataPersisterClassNameDetails->getFullName(), + 'api/DataPersister.tpl.php', + $templateVariables + ); + + $generator->writeChanges(); + + $this->writeSuccessMessage($io); + + $io->text([ + 'Next:', + sprintf('- Open the %s class and add the code you need', $dataPersisterClassNameDetails->getFullName()), + 'Find the documentation at https://api-platform.com/docs/core/data-persisters/#creating-a-custom-data-persister', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies) + { + $dependencies->addClassDependency( + ContextAwareDataPersisterInterface::class, + 'api' + ); + } + + private function createResourceClassQuestion(string $questionText): Question + { + $question = new Question($questionText); + $question->setAutocompleterValues(array_values($this->getResources())); + + return $question; + } + + private function getResources(): array + { + $collection = $this->ressourceNameCollection->create(); + $resources = []; + + foreach ($collection as $className) { + $resources[$className] = $this->ressourceMetaDataFactory->create($className)->getShortName(); + } + + return $resources; + } +} diff --git a/src/Resources/config/makers.xml b/src/Resources/config/makers.xml index e17b72538..b6638e81e 100644 --- a/src/Resources/config/makers.xml +++ b/src/Resources/config/makers.xml @@ -123,5 +123,13 @@ %kernel.project_dir% + + + + + + + + diff --git a/src/Resources/help/MakeDataPersister.txt b/src/Resources/help/MakeDataPersister.txt new file mode 100644 index 000000000..d7f066299 --- /dev/null +++ b/src/Resources/help/MakeDataPersister.txt @@ -0,0 +1,5 @@ +The %command.name% command generates a new Data Persister class. + +php %command.full_name% CustomDataPersister + +If the argument is missing, the command will ask for the message class interactively. \ No newline at end of file diff --git a/src/Resources/skeleton/api/DataPersister.tpl.php b/src/Resources/skeleton/api/DataPersister.tpl.php new file mode 100644 index 000000000..d4de54e45 --- /dev/null +++ b/src/Resources/skeleton/api/DataPersister.tpl.php @@ -0,0 +1,45 @@ + + +namespace ; + +use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; + + +final class implements ContextAwareDataPersisterInterface +{ + + private $decorated; + + public function __construct(ContextAwareDataPersisterInterface $decorated) + { + $this->decorated = $decorated; + } + + + public function supports($data, array $context = []): bool + { + + return $this->decorated->supports($data, $context); + + return $data instanceof ; + + return true; + + } + + public function persist($data, array $context = []) + { + + $data = $this->decorated->persist($data, $context); + + + return $data; + } + + public function remove($data, array $context = []) + { + + return $this->decorated->persist($data, $context); + + } +} diff --git a/tests/Maker/MakeDataPersisterTest.php b/tests/Maker/MakeDataPersisterTest.php new file mode 100644 index 000000000..26fc35a38 --- /dev/null +++ b/tests/Maker/MakeDataPersisterTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Tests\Maker; + +use Symfony\Bundle\MakerBundle\Maker\MakeDataPersister; +use Symfony\Bundle\MakerBundle\Test\MakerTestCase; +use Symfony\Bundle\MakerBundle\Test\MakerTestDetails; + +class MakeDataPersisterTest extends MakerTestCase +{ + public function getTestDetails() + { + yield 'api_data_persister' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeDataPersister::class), + [ + // data persister name + 'CustomDataPersister', + ' ', + ])->assert(function (string $output, string $directory) { + $this->assertFileExists($directory.'/src/DataPersister/CustomDataPersister.php'); + }), + ]; + yield 'entity_with_doctrine_persister' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeDataPersister::class), + [ + 'ArticleDataPersister', + 'Article', + 'yes', + ]) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeDataPersister') + ->assert(function (string $output, string $directory) { + $this->assertFileExists($directory.'/src/DataPersister/ArticleDataPersister.php'); + }), + ]; + yield 'entity_without_doctrine_persister' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeDataPersister::class), + [ + 'ArticleBlogDataPersister', + 'Article', + ]) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeDataPersister') + ->assert(function (string $output, string $directory) { + $this->assertFileExists($directory.'/src/DataPersister/ArticleBlogDataPersister.php'); + }), + ]; + yield 'model_class_persister' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeDataPersister::class), + [ + 'BookDataPersister', + 'Book', + ]) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeDataPersister') + ->assert(function (string $output, string $directory) { + $this->assertFileExists($directory.'/src/DataPersister/BookDataPersister.php'); + }), + ]; + } +} diff --git a/tests/fixtures/MakeDataPersister/Model/Book.php b/tests/fixtures/MakeDataPersister/Model/Book.php new file mode 100644 index 000000000..bbf4263f0 --- /dev/null +++ b/tests/fixtures/MakeDataPersister/Model/Book.php @@ -0,0 +1,32 @@ +id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } +} diff --git a/tests/fixtures/MakeDataPersister/src/Entity/Article.php b/tests/fixtures/MakeDataPersister/src/Entity/Article.php new file mode 100644 index 000000000..a32fa07f3 --- /dev/null +++ b/tests/fixtures/MakeDataPersister/src/Entity/Article.php @@ -0,0 +1,42 @@ +id; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } +}