From 3fa37c993e451d2bd26a4fa0166340df8010617e Mon Sep 17 00:00:00 2001 From: romainallanot Date: Wed, 9 Jun 2021 17:06:06 +0200 Subject: [PATCH 1/3] Add Rector command, rules and tests --- composer.json | 1 + .../Resolver/OperationClassResolver.php | 45 ++++ ...rceAnnotationToResourceAttributeRector.php | 225 ++++++++++++++++++ ...rade-annotation-to-attribute-v2-and-v3.php | 46 ++++ .../upgrade-annotation-to-attribute-v2.php | 31 +++ .../upgrade-annotation-to-attribute-v3.php | 32 +++ .../Symfony/Bundle/Command/RectorCommand.php | 79 ++++++ .../ApiPlatformExtension.php | 6 + .../Bundle/Resources/config/rector.xml | 13 + ...nnotationToResourceAttributeRectorTest.php | 31 +++ .../Fixture/absolute_url_dummy.php.inc | 30 +++ .../Fixture/book.php.inc | 34 +++ .../Fixture/custom_action_dummy.php.inc | 49 ++++ .../Fixture/disable_item_operation.php.inc | 45 ++++ .../Fixture/dummy_custom_mutation.php.inc | 58 +++++ .../Fixture/dummy_dto_no_input.php.inc | 66 +++++ .../Fixture/minimal.php.inc | 28 +++ .../Fixture/not_a_resource.php.inc | 19 ++ .../config/configured_rule.php | 29 +++ 19 files changed, 867 insertions(+) create mode 100644 src/Bridge/Rector/Resolver/OperationClassResolver.php create mode 100644 src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php create mode 100644 src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php create mode 100644 src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php create mode 100644 src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php create mode 100644 src/Bridge/Symfony/Bundle/Command/RectorCommand.php create mode 100644 src/Bridge/Symfony/Bundle/Resources/config/rector.xml create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/ApiResourceAnnotationToResourceAttributeRectorTest.php create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/disable_item_operation.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/minimal.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/not_a_resource.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/config/configured_rule.php diff --git a/composer.json b/composer.json index c883dbe9f73..9beaf24523b 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,7 @@ "psr/log": "^1.0", "ramsey/uuid": "^3.7 || ^4.0", "ramsey/uuid-doctrine": "^1.4", + "rector/rector": "^0.11.7", "soyuka/contexts": "^3.3.1", "soyuka/stubs-mongodb": "^1.0", "symfony/asset": "^3.4 || ^4.4 || ^5.1", diff --git a/src/Bridge/Rector/Resolver/OperationClassResolver.php b/src/Bridge/Rector/Resolver/OperationClassResolver.php new file mode 100644 index 00000000000..2b3a20481c2 --- /dev/null +++ b/src/Bridge/Rector/Resolver/OperationClassResolver.php @@ -0,0 +1,45 @@ + [ + 'get' => Get::class, + 'put' => Put::class, + 'patch' => Patch::class, + 'delete' => Delete::class, + 'post' => Post::class, + ], + 'collectionOperations' => [ + 'get' => GetCollection::class, + 'post' => Post::class, + ], + ]; + + public static function resolve($operationName, $operationType, &$items) + { + if (array_key_exists($operationName, self::$operationsClass[$operationType])) { + return self::$operationsClass[$operationType][$operationName]; + } + + if (isset($items['method'])) { + $method = strtolower($items['method']); + unset($items['method']); + + if (isset(self::$operationsClass[$operationType][$method])) { + return self::$operationsClass[$operationType][$method]; + } + } + + throw new \Exception(sprintf('Unable to resolve operation class for %s "%s"', $operationType, $operationName)); + } +} diff --git a/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php new file mode 100644 index 00000000000..f6d9949813c --- /dev/null +++ b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php @@ -0,0 +1,225 @@ +phpAttributeGroupFactory = $phpAttributeGroupFactory; + $this->phpDocTagRemover = $phpDocTagRemover; + } + + public function getRuleDefinition() : RuleDefinition + { + return new RuleDefinition('Change annotation to attribute', [new ConfiguredCodeSample(<<<'CODE_SAMPLE' +use ApiPlatform\Core\Annotation\ApiResource; + +/** + * @ApiResource(collectionOperations={}, itemOperations={ + * "get", + * "get_by_isbn"={"method"="GET", "path"="/books/by_isbn/{isbn}.{_format}", "requirements"={"isbn"=".+"}, "identifiers"="isbn"} + * }) + */ +class Book +CODE_SAMPLE + , <<<'CODE_SAMPLE' +use ApiPlatform\Metadata\Resource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Core\Annotation\ApiResource; + +#[Resource] +#[Get] +#[Get(operationName: 'get_by_isbn', path: '/books/by_isbn/{isbn}.{_format}', requirements: ['isbn' => '.+'], identifiers: 'isbn')] +class Book +CODE_SAMPLE + , [ + self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute('ApiPlatform\\Core\\Annotation\\ApiResource', 'ApiPlatform\\Core\\Annotation\\ApiResource')], + self::REMOVE_TAG => true, + ]) + ]); + } + + /** + * @return array> + */ + public function getNodeTypes() : array + { + return [Class_::class, Property::class, ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; + } + + /** + * @param Class_|Property|ClassMethod|Function_|Closure|ArrowFunction $node + */ + public function refactor(Node $node) : ?Node + { + if (!$this->isAtLeastPhpVersion(PhpVersionFeature::ATTRIBUTES)) { + return null; + } + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (!$phpDocInfo instanceof PhpDocInfo) { + return null; + } + $tags = $phpDocInfo->getAllTags(); + $hasNewAttrGroups = $this->processApplyAttrGroups($tags, $phpDocInfo, $node); + if ($hasNewAttrGroups) { + return $node; + } + return null; + } + + /** + * @param array $configuration + */ + public function configure(array $configuration) : void + { + $annotationsToAttributes = $configuration[self::ANNOTATION_TO_ATTRIBUTE] ?? []; + Assert::allIsInstanceOf($annotationsToAttributes, AnnotationToAttribute::class); + $this->annotationsToAttributes = $annotationsToAttributes; + $this->removeTag = $configuration[self::REMOVE_TAG] ?? true; + } + + /** + * @param array $tags + * @param Class_|Property|ClassMethod|Function_|Closure|ArrowFunction $node + */ + private function processApplyAttrGroups(array $tags, PhpDocInfo $phpDocInfo, Node $node) : bool + { + $hasNewAttrGroups = \false; + foreach ($tags as $tag) { + foreach ($this->annotationsToAttributes as $annotationToAttribute) { + $annotationToAttributeTag = $annotationToAttribute->getTag(); + if ($phpDocInfo->hasByName($annotationToAttributeTag)) { + if (true === $this->removeTag) { + // 1. remove php-doc tag + $this->phpDocTagRemover->removeByName($phpDocInfo, $annotationToAttributeTag); + } + // 2. add attributes + $node->attrGroups[] = $this->phpAttributeGroupFactory->createFromSimpleTag($annotationToAttribute); + $hasNewAttrGroups = \true; + continue 2; + } + if ($this->shouldSkip($tag->value, $phpDocInfo, $annotationToAttributeTag)) { + continue; + } + + if (true === $this->removeTag) { + // 1. remove php-doc tag + $this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $tag->value); + } + // 2. add attributes + /** @var DoctrineAnnotationTagValueNode $tagValue */ + $tagValue = clone $tag->value; + $this->resolveOperations($tagValue, $node); + $resourceAttributeGroup = $this->phpAttributeGroupFactory->create($tagValue, $annotationToAttribute); + array_unshift($node->attrGroups, $resourceAttributeGroup); + $hasNewAttrGroups = \true; + continue 2; + } + } + return $hasNewAttrGroups; + } + + private function shouldSkip(PhpDocTagValueNode $phpDocTagValueNode, PhpDocInfo $phpDocInfo, string $annotationToAttributeTag) : bool + { + $doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClass($annotationToAttributeTag); + if ($phpDocTagValueNode !== $doctrineAnnotationTagValueNode) { + return \true; + } + return !$phpDocTagValueNode instanceof DoctrineAnnotationTagValueNode; + } + + private function resolveOperations(DoctrineAnnotationTagValueNode $tagValue, $node) + { + $values = $tagValue->getValues(); + + foreach (['collectionOperations', 'itemOperations'] as $operationType) { + if (isset($values[$operationType])) { + foreach ($values[$operationType]->getValuesWithExplicitSilentAndWithoutQuotes() as $operationName => $items) { + /** + * For this cases: + * itemOperations={ + * "get_by_isbn"={"method"="GET", "path"="/books/by_isbn/{isbn}.{_format}", "requirements"={"isbn"=".+"}, "identifiers"="isbn"} + * } + */ + if (is_array($items)) { + $items = ['operationName' => $operationName] + $items; + + foreach ($items as $key => $item) { + $camelizedKey = (string) (new UnicodeString($key))->camel(); + if ($key === $camelizedKey) { + continue; + } + $items[$camelizedKey] = $items[$key]; + unset($items[$key]); + } + } + /** + * For this cases: + * collectionOperations={"get", "post"}, + * itemOperations={"get", "put", "delete"}, + */ + if (is_string($items)) { + $operationName = $items; + $items = []; + } + + $operationClass = OperationClassResolver::resolve($operationName, $operationType, $items); + $attribute = $this->phpAttributeGroupFactory->createFromClassWithItems($operationClass, $items); + + $node->attrGroups[] = $attribute; + } + // Remove collectionOperations/itemOperations from Tag values + $tagValue->removeValue($operationType); + } + } + } +} diff --git a/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php new file mode 100644 index 00000000000..33fb1acc60b --- /dev/null +++ b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php @@ -0,0 +1,46 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + + // ApiResource annotation to Resource & operation attributes + $services->set(ApiResourceAnnotationToResourceAttributeRector::class) + ->call('configure', [[ + ApiResourceAnnotationToResourceAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute( + ApiResource::class, + Resource::class + ), + ]), + ApiResourceAnnotationToResourceAttributeRector::REMOVE_TAG => false, + ]]) + ; + + // ApiResource annotation to ApiResource attribute + $services->set(AnnotationToAttributeRector::class) + ->call('configure', [[ + AnnotationToAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute( + ApiResource::class, + ApiResource::class + ), + ]), + ]]) + ; +}; diff --git a/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php new file mode 100644 index 00000000000..24a437d5a26 --- /dev/null +++ b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php @@ -0,0 +1,31 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + + // ApiResource annotation to ApiResource attribute + $services->set(AnnotationToAttributeRector::class) + ->call('configure', [[ + AnnotationToAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute( + ApiResource::class, + ApiResource::class + ), + ]), + ]]) + ; +}; diff --git a/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php new file mode 100644 index 00000000000..1798ddf4a28 --- /dev/null +++ b/src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php @@ -0,0 +1,32 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + + // ApiResource annotation to Resource & operation attributes + $services->set(ApiResourceAnnotationToResourceAttributeRector::class) + ->call('configure', [[ + ApiResourceAnnotationToResourceAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute( + ApiResource::class, + Resource::class + ), + ]), + ]]) + ; +}; diff --git a/src/Bridge/Symfony/Bundle/Command/RectorCommand.php b/src/Bridge/Symfony/Bundle/Command/RectorCommand.php new file mode 100644 index 00000000000..1b05e2ccb11 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Command/RectorCommand.php @@ -0,0 +1,79 @@ +setDescription('Change ApiResource annotation to attribute') + ->addOption('dry-run', '-d', InputOption::VALUE_NONE, 'Rector will show you diff of files that it would change. To make the changes, drop --dry-run') + ->addArgument('src', InputArgument::REQUIRED, '') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $choice = $io->choice('Choose operation to perform', [ + 1 => self::ANNOTATION_TO_ATTRIBUTE_V2, + self::ANNOTATION_TO_ATTRIBUTE_V3, + self::ANNOTATION_TO_ATTRIBUTE_V2_AND_V3, +// self::ATTRIBUTE_V2_TO_V3, + ]); + + $command = 'vendor/bin/rector process '.$input->getArgument('src'); + + if ($input->getOption('dry-run')) { + $command .= ' --dry-run'; + } else { + $io->confirm('Confirm ?'); + } + + if ($output->isDebug()) { + $command .= ' --debug'; + } + + switch ($choice) { + case self::ANNOTATION_TO_ATTRIBUTE_V2; + $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php'; + break; + case self::ANNOTATION_TO_ATTRIBUTE_V3; + $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php'; + break; + case self::ANNOTATION_TO_ATTRIBUTE_V2_AND_V3; + $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php'; + break; +// case self::ATTRIBUTE_V2_TO_V3; +// break; + } + + $io->title('Run '.$command); + passthru($command); + + return Command::SUCCESS; + } +} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index d19f5b24834..4c3762f01af 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -125,6 +125,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerElasticsearchConfiguration($container, $config, $loader); $this->registerDataTransformerConfiguration($container); $this->registerSecurityConfiguration($container, $loader); + $this->registerRectorConfiguration($container, $loader); $container->registerForAutoconfiguration(DataPersisterInterface::class) ->addTag('api_platform.data_persister'); @@ -730,6 +731,11 @@ private function registerOpenApiConfiguration(ContainerBuilder $container, array $container->setParameter('api_platform.openapi.license.url', $config['openapi']['license']['url']); } + private function registerRectorConfiguration(ContainerBuilder $container, XmlFileLoader $loader): void + { + $loader->load('rector.xml'); + } + private function buildDeprecationArgs(string $version, string $message): array { return method_exists(Definition::class, 'getDeprecation') diff --git a/src/Bridge/Symfony/Bundle/Resources/config/rector.xml b/src/Bridge/Symfony/Bundle/Resources/config/rector.xml new file mode 100644 index 00000000000..fff2004f0ac --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Resources/config/rector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/ApiResourceAnnotationToResourceAttributeRectorTest.php b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/ApiResourceAnnotationToResourceAttributeRectorTest.php new file mode 100644 index 00000000000..ca0577c8977 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/ApiResourceAnnotationToResourceAttributeRectorTest.php @@ -0,0 +1,31 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc new file mode 100644 index 00000000000..eb024440c5f --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc new file mode 100644 index 00000000000..9497fdb878f --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc @@ -0,0 +1,34 @@ + +----- + '.+'], identifiers: 'isbn')] +class Book +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc new file mode 100644 index 00000000000..2ebb64a0699 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc @@ -0,0 +1,49 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/disable_item_operation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/disable_item_operation.php.inc new file mode 100644 index 00000000000..d8001aa9784 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/disable_item_operation.php.inc @@ -0,0 +1,45 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc new file mode 100644 index 00000000000..df909e45aa3 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc @@ -0,0 +1,58 @@ + +----- + ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNotPersisted' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_not_persisted', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNoWriteCustomResult' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_no_write_custom_result', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'write' => false], 'sumOnlyPersist' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_only_persist', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'read' => false, 'deserialize' => false, 'validate' => false, 'serialize' => false], 'testCustomArguments' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'args' => ['operandC' => ['type' => 'Int!']]]])] +class DummyCustomMutation +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc new file mode 100644 index 00000000000..bfad76a8e7b --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc @@ -0,0 +1,66 @@ + +----- + false, 'output' => OutputDto::class])] +#[Post(operationName: 'post', method: 'POST', path: '/dummy_dto_no_inputs', controller: CreateItemAction::class)] +#[GetCollection] +#[Get] +#[Delete] +#[Post(operationName: 'post_double_bat', path: '/dummy_dto_no_inputs/{id}/double_bat', controller: DoubleBatAction::class, status: 200)] +class DummyDtoNoInput +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/minimal.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/minimal.php.inc new file mode 100644 index 00000000000..934319acf47 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/minimal.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/not_a_resource.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/not_a_resource.php.inc new file mode 100644 index 00000000000..8508113e718 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/not_a_resource.php.inc @@ -0,0 +1,19 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/config/configured_rule.php b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/config/configured_rule.php new file mode 100644 index 00000000000..70b3717cfe8 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/config/configured_rule.php @@ -0,0 +1,29 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + $services->set(ApiResourceAnnotationToResourceAttributeRector::class) + ->call('configure', [[ + ApiResourceAnnotationToResourceAttributeRector::ANNOTATION_TO_ATTRIBUTE => ValueObjectInliner::inline([ + new AnnotationToAttribute( + ApiResource::class, + Resource::class + ), + ]), + ]]); +}; From 9d690fba047139418a96f0aa572c9446d072f4cc Mon Sep 17 00:00:00 2001 From: romainallanot Date: Thu, 10 Jun 2021 17:51:07 +0200 Subject: [PATCH 2/3] Add ApiResourceAttributeToResourceAttribute Rule --- .../Resolver/OperationClassResolver.php | 11 +- ...AbstractApiResourceToResourceAttribute.php | 70 ++++++++++ ...rceAnnotationToResourceAttributeRector.php | 74 +++------- ...urceAttributeToResourceAttributeRector.php | 126 ++++++++++++++++++ src/Bridge/Rector/Set/ApiPlatformSetList.php | 27 ++++ ...o-api-resource-and-resource-attribute.php} | 0 ... annotation-to-api-resource-attribute.php} | 0 ...p => annotation-to-resource-attribute.php} | 0 .../sets/attribute-to-resource-attribute.php | 18 +++ .../Symfony/Bundle/Command/RectorCommand.php | 18 +-- .../Fixture/book.php.inc | 2 +- .../Fixture/custom_action_dummy.php.inc | 4 +- .../Fixture/dummy_dto_no_input.php.inc | 4 +- ...AttributeToResourceAttributeRectorTest.php | 31 +++++ .../Fixture/absolute_url_dummy.php.inc | 28 ++++ .../Fixture/book.php.inc | 29 ++++ .../Fixture/custom_action_dummy.php.inc | 49 +++++++ .../Fixture/disable_item_operation.php.inc | 45 +++++++ .../Fixture/dummy_custom_mutation.php.inc | 58 ++++++++ .../Fixture/dummy_dto_no_input.php.inc | 66 +++++++++ .../Fixture/minimal.php.inc | 28 ++++ .../Fixture/not_a_resource.php.inc | 19 +++ .../Source/Book.php | 11 ++ .../config/configured_rule.php | 20 +++ 24 files changed, 665 insertions(+), 73 deletions(-) create mode 100644 src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php create mode 100644 src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php create mode 100644 src/Bridge/Rector/Set/ApiPlatformSetList.php rename src/Bridge/Rector/config/sets/{upgrade-annotation-to-attribute-v2-and-v3.php => annotation-to-api-resource-and-resource-attribute.php} (100%) rename src/Bridge/Rector/config/sets/{upgrade-annotation-to-attribute-v2.php => annotation-to-api-resource-attribute.php} (100%) rename src/Bridge/Rector/config/sets/{upgrade-annotation-to-attribute-v3.php => annotation-to-resource-attribute.php} (100%) create mode 100644 src/Bridge/Rector/config/sets/attribute-to-resource-attribute.php create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/ApiResourceAttributeToResourceAttributeRectorTest.php create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/custom_action_dummy.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/not_a_resource.php.inc create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php create mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/config/configured_rule.php diff --git a/src/Bridge/Rector/Resolver/OperationClassResolver.php b/src/Bridge/Rector/Resolver/OperationClassResolver.php index 2b3a20481c2..d9e7b10b5f9 100644 --- a/src/Bridge/Rector/Resolver/OperationClassResolver.php +++ b/src/Bridge/Rector/Resolver/OperationClassResolver.php @@ -1,5 +1,7 @@ [ 'get' => Get::class, 'put' => Put::class, @@ -25,15 +27,14 @@ class OperationClassResolver ], ]; - public static function resolve($operationName, $operationType, &$items) + public static function resolve(string $operationName, string $operationType, array $arguments): string { if (array_key_exists($operationName, self::$operationsClass[$operationType])) { return self::$operationsClass[$operationType][$operationName]; } - if (isset($items['method'])) { - $method = strtolower($items['method']); - unset($items['method']); + if (isset($arguments['method'])) { + $method = strtolower($arguments['method']); if (isset(self::$operationsClass[$operationType][$method])) { return self::$operationsClass[$operationType][$method]; diff --git a/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php b/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php new file mode 100644 index 00000000000..b02e689657e --- /dev/null +++ b/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php @@ -0,0 +1,70 @@ + $arguments) { + /** + * Case of custom action, ex: + * itemOperations={ + * "get_by_isbn"={"method"="GET", "path"="/books/by_isbn/{isbn}.{_format}", "requirements"={"isbn"=".+"}, "identifiers"="isbn"} + * } + */ + if (is_array($arguments)) { + // add operation name + $arguments = ['operationName' => $name] + $arguments; + foreach ($arguments as $key => $argument) { + // camelize argument name + $camelizedKey = (string) (new UnicodeString($key))->camel(); + if ($key === $camelizedKey) { + continue; + } + $arguments[$camelizedKey] = $argument; + unset($arguments[$key]); + } + } + + /** + * Case of default action, ex: + * collectionOperations={"get", "post"}, + * itemOperations={"get", "put", "delete"}, + */ + if (is_string($arguments)) { + unset($operations[$name]); + $name = $arguments; + $arguments = []; + } + + $operations[$name] = $arguments; + } + + return $operations; + } + + protected function createOperationAttributeGroup(string $type, string $name, array $arguments): AttributeGroup + { + $operationClass = OperationClassResolver::resolve($name, $type, $arguments); + + // remove unnecessary argument "method" after resolving the operation class + if (isset($arguments['method'])) { + unset($arguments['method']); + } + + return $this->phpAttributeGroupFactory->createFromClassWithItems($operationClass, $arguments); + } +} diff --git a/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php index f6d9949813c..3b753be7a7c 100644 --- a/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php +++ b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php @@ -2,30 +2,23 @@ namespace ApiPlatform\Core\Bridge\Rector\Rules; -use ApiPlatform\Core\Bridge\Rector\Resolver\OperationClassResolver; +use ApiPlatform\Core\Annotation\ApiResource; use PhpParser\Node; -use PhpParser\Node\Expr\ArrowFunction; -use PhpParser\Node\Expr\Closure; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTagRemover; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; -use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\Php80\ValueObject\AnnotationToAttribute; use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; -use Symfony\Component\String\UnicodeString; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; -final class ApiResourceAnnotationToResourceAttributeRector extends AbstractRector implements ConfigurableRectorInterface +final class ApiResourceAnnotationToResourceAttributeRector extends AbstractApiResourceToResourceAttribute implements ConfigurableRectorInterface { /** * @var string @@ -43,10 +36,6 @@ final class ApiResourceAnnotationToResourceAttributeRector extends AbstractRecto * @var bool */ private $removeTag; - /** - * @var PhpAttributeGroupFactory - */ - private $phpAttributeGroupFactory; /** * @var PhpDocTagRemover */ @@ -82,7 +71,7 @@ class Book class Book CODE_SAMPLE , [ - self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute('ApiPlatform\\Core\\Annotation\\ApiResource', 'ApiPlatform\\Core\\Annotation\\ApiResource')], + self::ANNOTATION_TO_ATTRIBUTE => [new AnnotationToAttribute(ApiResource::class, ApiResource::class)], self::REMOVE_TAG => true, ]) ]); @@ -93,11 +82,11 @@ class Book */ public function getNodeTypes() : array { - return [Class_::class, Property::class, ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; + return [Class_::class]; } /** - * @param Class_|Property|ClassMethod|Function_|Closure|ArrowFunction $node + * @param Class_ $node */ public function refactor(Node $node) : ?Node { @@ -129,7 +118,7 @@ public function configure(array $configuration) : void /** * @param array $tags - * @param Class_|Property|ClassMethod|Function_|Closure|ArrowFunction $node + * @param Class_ $node */ private function processApplyAttrGroups(array $tags, PhpDocInfo $phpDocInfo, Node $node) : bool { @@ -165,6 +154,7 @@ private function processApplyAttrGroups(array $tags, PhpDocInfo $phpDocInfo, Nod continue 2; } } + return $hasNewAttrGroups; } @@ -174,51 +164,25 @@ private function shouldSkip(PhpDocTagValueNode $phpDocTagValueNode, PhpDocInfo $ if ($phpDocTagValueNode !== $doctrineAnnotationTagValueNode) { return \true; } + return !$phpDocTagValueNode instanceof DoctrineAnnotationTagValueNode; } - private function resolveOperations(DoctrineAnnotationTagValueNode $tagValue, $node) + /** + * @param Class_ $node + */ + private function resolveOperations(DoctrineAnnotationTagValueNode $tagValue, Node $node): void { $values = $tagValue->getValues(); - foreach (['collectionOperations', 'itemOperations'] as $operationType) { - if (isset($values[$operationType])) { - foreach ($values[$operationType]->getValuesWithExplicitSilentAndWithoutQuotes() as $operationName => $items) { - /** - * For this cases: - * itemOperations={ - * "get_by_isbn"={"method"="GET", "path"="/books/by_isbn/{isbn}.{_format}", "requirements"={"isbn"=".+"}, "identifiers"="isbn"} - * } - */ - if (is_array($items)) { - $items = ['operationName' => $operationName] + $items; - - foreach ($items as $key => $item) { - $camelizedKey = (string) (new UnicodeString($key))->camel(); - if ($key === $camelizedKey) { - continue; - } - $items[$camelizedKey] = $items[$key]; - unset($items[$key]); - } - } - /** - * For this cases: - * collectionOperations={"get", "post"}, - * itemOperations={"get", "put", "delete"}, - */ - if (is_string($items)) { - $operationName = $items; - $items = []; - } - - $operationClass = OperationClassResolver::resolve($operationName, $operationType, $items); - $attribute = $this->phpAttributeGroupFactory->createFromClassWithItems($operationClass, $items); - - $node->attrGroups[] = $attribute; + foreach ($this->operationTypes as $type) { + if (isset($values[$type])) { + $operations = $this->formatOperations($values[$type]->getValuesWithExplicitSilentAndWithoutQuotes()); + foreach ($operations as $name => $arguments) { + $node->attrGroups[] = $this->createOperationAttributeGroup($type, $name, $arguments); } - // Remove collectionOperations/itemOperations from Tag values - $tagValue->removeValue($operationType); + // Remove collectionOperations|itemOperations from Tag values + $tagValue->removeValue($type); } } } diff --git a/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php b/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php new file mode 100644 index 00000000000..ef20d717b91 --- /dev/null +++ b/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php @@ -0,0 +1,126 @@ +phpAttributeGroupFactory = $phpAttributeGroupFactory; + } + + public function getRuleDefinition() : RuleDefinition + { + return new RuleDefinition('Upgrade ApiResource attribute to Resource and Operations attributes', [new ConfiguredCodeSample(<<<'CODE_SAMPLE' +use ApiPlatform\Core\Annotation\ApiResource; + +#[ApiResource(collectionOperations: [], itemOperations: ['get', 'get_by_isbn' => ['method' => 'GET', 'path' => '/books/by_isbn/{isbn}.{_format}', 'requirements' => ['isbn' => '.+'], 'identifiers' => 'isbn']])] +class Book +CODE_SAMPLE + , <<<'CODE_SAMPLE' +use ApiPlatform\Metadata\Resource; +use ApiPlatform\Metadata\Get; + +#[Resource] +#[Get] +#[Get(operationName: 'get_by_isbn', path: '/books/by_isbn/{isbn}.{_format}', requirements: ['isbn' => '.+'], identifiers: 'isbn')] +class Book +CODE_SAMPLE + , [self::REMOVE_INITIAL_ATTRIBUTE => true])]); + } + + /** + * @return array> + */ + public function getNodeTypes() : array + { + return [Class_::class]; + } + + /** + * @param array $configuration + */ + public function configure(array $configuration) : void + { + $this->removeInitialAttribute = $configuration[self::REMOVE_INITIAL_ATTRIBUTE] ?? true; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node) : ?Node + { + $reflectionClass = new \ReflectionClass($node->name->getAttribute('className')); + + foreach ($reflectionClass->getAttributes() as $attribute) { + if (ApiResource::class !== $attribute->getName()) { + continue; + } + + $arguments = $this->resolveOperations($attribute->getArguments(), $node); + $resourceAttributeGroup = $this->phpAttributeGroupFactory->createFromClassWithItems(Resource::class, $arguments); + array_unshift($node->attrGroups, $resourceAttributeGroup); + } + + $this->cleanupAttrGroups($node); + + return $node; + } + + /** + * Remove initial ApiResource attribute from node + * + * @param Class_ $node + */ + private function cleanupAttrGroups(Node $node) : void + { + if (false === $this->removeInitialAttribute) { + return; + } + + foreach ($node->attrGroups as $key => $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + if ($this->isName($attribute->name, ApiResource::class)) { + unset($node->attrGroups[$key]); + continue(2); + } + } + } + } + + /** + * @param Class_ $node + */ + private function resolveOperations(array $values, Node $node): array + { + foreach ($this->operationTypes as $type) { + if (isset($values[$type])) { + $operations = $this->formatOperations($values[$type]); + foreach ($operations as $name => $arguments) { + $node->attrGroups[] = $this->createOperationAttributeGroup($type, $name, $arguments); + } + unset($values[$type]); + } + } + + return $values; + } +} diff --git a/src/Bridge/Rector/Set/ApiPlatformSetList.php b/src/Bridge/Rector/Set/ApiPlatformSetList.php new file mode 100644 index 00000000000..895e771c2b1 --- /dev/null +++ b/src/Bridge/Rector/Set/ApiPlatformSetList.php @@ -0,0 +1,27 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + + $services->set(ApiResourceAttributeToResourceAttributeRector::class); +}; diff --git a/src/Bridge/Symfony/Bundle/Command/RectorCommand.php b/src/Bridge/Symfony/Bundle/Command/RectorCommand.php index 1b05e2ccb11..d2d004e4211 100644 --- a/src/Bridge/Symfony/Bundle/Command/RectorCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/RectorCommand.php @@ -4,6 +4,7 @@ namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Command; +use ApiPlatform\Core\Bridge\Rector\Set\ApiPlatformSetList; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -16,7 +17,7 @@ class RectorCommand extends Command private const ANNOTATION_TO_ATTRIBUTE_V2 = '@ApiResource to #[ApiResource]'; private const ANNOTATION_TO_ATTRIBUTE_V3 = '@ApiResource to #[Resource]'; private const ANNOTATION_TO_ATTRIBUTE_V2_AND_V3 = '@ApiResource to #[ApiResource] and #[Resource]'; -// private const ATTRIBUTE_V2_TO_V3 = '#[ApiResource] to #[Resource]'; + private const ATTRIBUTE_V2_TO_V3 = '#[ApiResource] to #[Resource]'; protected static $defaultName = 'api:rector:upgrade'; @@ -26,7 +27,7 @@ class RectorCommand extends Command protected function configure() { $this - ->setDescription('Change ApiResource annotation to attribute') + ->setDescription('Change ApiResource annotation/attribute to ApiResource/Resource attribute') ->addOption('dry-run', '-d', InputOption::VALUE_NONE, 'Rector will show you diff of files that it would change. To make the changes, drop --dry-run') ->addArgument('src', InputArgument::REQUIRED, '') ; @@ -42,7 +43,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int 1 => self::ANNOTATION_TO_ATTRIBUTE_V2, self::ANNOTATION_TO_ATTRIBUTE_V3, self::ANNOTATION_TO_ATTRIBUTE_V2_AND_V3, -// self::ATTRIBUTE_V2_TO_V3, + self::ATTRIBUTE_V2_TO_V3, ]); $command = 'vendor/bin/rector process '.$input->getArgument('src'); @@ -59,16 +60,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int switch ($choice) { case self::ANNOTATION_TO_ATTRIBUTE_V2; - $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2.php'; + $command .= ' --config='.ApiPlatformSetList::ANNOTATION_TO_API_RESOURCE_ATTRIBUTE; break; case self::ANNOTATION_TO_ATTRIBUTE_V3; - $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v3.php'; + $command .= ' --config='.ApiPlatformSetList::ANNOTATION_TO_RESOURCE_ATTRIBUTE; break; case self::ANNOTATION_TO_ATTRIBUTE_V2_AND_V3; - $command .= ' --config=src/Bridge/Rector/config/sets/upgrade-annotation-to-attribute-v2-and-v3.php'; + $command .= ' --config='.ApiPlatformSetList::ANNOTATION_TO_API_RESOURCE_AND_RESOURCE_ATTRIBUTE; + break; + case self::ATTRIBUTE_V2_TO_V3; + $command .= ' --config='.ApiPlatformSetList::ATTRIBUTE_TO_RESOURCE_ATTRIBUTE; break; -// case self::ATTRIBUTE_V2_TO_V3; -// break; } $io->title('Run '.$command); diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc index 9497fdb878f..b1c17a609ff 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/book.php.inc @@ -25,8 +25,8 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Core\Annotation\ApiResource; #[Resource] -#[Get] #[Get(operationName: 'get_by_isbn', path: '/books/by_isbn/{isbn}.{_format}', requirements: ['isbn' => '.+'], identifiers: 'isbn')] +#[Get] class Book { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc index 2ebb64a0699..d4f768dbee8 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/custom_action_dummy.php.inc @@ -34,14 +34,14 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Core\Annotation\ApiResource; #[Resource] -#[GetCollection] #[GetCollection(operationName: 'get_custom', path: 'custom_action_collection_dummies')] #[GetCollection(operationName: 'custom_denormalization', routeName: 'custom_denormalization')] #[GetCollection(operationName: 'short_custom_denormalization', routeName: 'short_custom_denormalization')] -#[Get] +#[GetCollection] #[Get(operationName: 'get_custom', path: 'custom_action_collection_dummies/{id}')] #[Get(operationName: 'custom_normalization', routeName: 'custom_normalization')] #[Get(operationName: 'short_custom_normalization', routeName: 'short_custom_normalization')] +#[Get] class CustomActionDummy { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc index bfad76a8e7b..588df489c0e 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc @@ -54,11 +54,11 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Controller\DummyDtoNoInput\Double use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\OutputDto; #[Resource(attributes: ['input' => false, 'output' => OutputDto::class])] -#[Post(operationName: 'post', method: 'POST', path: '/dummy_dto_no_inputs', controller: CreateItemAction::class)] +#[Post(operationName: 'post', path: '/dummy_dto_no_inputs', controller: CreateItemAction::class)] #[GetCollection] +#[Post(operationName: 'post_double_bat', path: '/dummy_dto_no_inputs/{id}/double_bat', controller: DoubleBatAction::class, status: 200)] #[Get] #[Delete] -#[Post(operationName: 'post_double_bat', path: '/dummy_dto_no_inputs/{id}/double_bat', controller: DoubleBatAction::class, status: 200)] class DummyDtoNoInput { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/ApiResourceAttributeToResourceAttributeRectorTest.php b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/ApiResourceAttributeToResourceAttributeRectorTest.php new file mode 100644 index 00000000000..319144765b0 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/ApiResourceAttributeToResourceAttributeRectorTest.php @@ -0,0 +1,31 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc new file mode 100644 index 00000000000..81cd80ef757 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/absolute_url_dummy.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc new file mode 100644 index 00000000000..8aa3be9837f --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc @@ -0,0 +1,29 @@ + ['method' => 'GET', 'path' => '/books/by_isbn/{isbn}.{_format}', 'requirements' => ['isbn' => '.+'], 'identifiers' => 'isbn']])] +class Book +{ +} + +?> +----- + '.+'], identifiers: 'isbn')] +#[Get] +class Book +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/custom_action_dummy.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/custom_action_dummy.php.inc new file mode 100644 index 00000000000..0013a92527b --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/custom_action_dummy.php.inc @@ -0,0 +1,49 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc new file mode 100644 index 00000000000..04e238fb793 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc @@ -0,0 +1,45 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc new file mode 100644 index 00000000000..e3dc563e389 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc @@ -0,0 +1,58 @@ + +----- + ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNotPersisted' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_not_persisted', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNoWriteCustomResult' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_no_write_custom_result', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'write' => false], 'sumOnlyPersist' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_only_persist', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'read' => false, 'deserialize' => false, 'validate' => false, 'serialize' => false], 'testCustomArguments' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'args' => ['operandC' => ['type' => 'Int!']]]])] +class DummyCustomMutation +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc new file mode 100644 index 00000000000..155d5e56a79 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc @@ -0,0 +1,66 @@ + +----- + false, 'output' => OutputDto::class])] +#[Post(operationName: 'post', path: '/dummy_dto_no_inputs', controller: CreateItemAction::class)] +#[GetCollection] +#[Post(operationName: 'post_double_bat', path: '/dummy_dto_no_inputs/{id}/double_bat', controller: DoubleBatAction::class, status: 200)] +#[Get] +#[Delete] +class DummyDtoNoInput +{ +} + +?> diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc new file mode 100644 index 00000000000..bf2040088bb --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/not_a_resource.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/not_a_resource.php.inc new file mode 100644 index 00000000000..9255703647c --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/not_a_resource.php.inc @@ -0,0 +1,19 @@ + +----- + diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php new file mode 100644 index 00000000000..1beb49368e7 --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php @@ -0,0 +1,11 @@ + ['method' => 'GET', 'path' => '/books/by_isbn/{isbn}.{_format}', 'requirements' => ['isbn' => '.+'], 'identifiers' => 'isbn']])] +class Book +{ + +} diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/config/configured_rule.php b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/config/configured_rule.php new file mode 100644 index 00000000000..fccd79c60cd --- /dev/null +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/config/configured_rule.php @@ -0,0 +1,20 @@ +parameters(); + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_80); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + $services = $containerConfigurator->services(); + $services->set(ApiResourceAttributeToResourceAttributeRector::class) + ->call('configure', [[ + ApiResourceAttributeToResourceAttributeRector::REMOVE_INITIAL_ATTRIBUTE => true + ]]); +}; From d7050d202200447c80cee97b2e0bac11a4e268b3 Mon Sep 17 00:00:00 2001 From: romainallanot Date: Fri, 11 Jun 2021 15:50:09 +0200 Subject: [PATCH 3/3] Add ApiResourceAttributeToResourceAttribute tests --- ...AbstractApiResourceToResourceAttribute.php | 2 +- ...rceAnnotationToResourceAttributeRector.php | 2 +- ...urceAttributeToResourceAttributeRector.php | 95 ++++++++++++++----- .../sets/attribute-to-resource-attribute.php | 6 +- .../Fixture/book.php.inc | 4 +- .../Fixture/custom_action_dummy.php.inc | 15 +-- .../Fixture/disable_item_operation.php.inc | 15 +-- .../Fixture/dummy_custom_mutation.php.inc | 34 +------ .../Fixture/dummy_dto_no_input.php.inc | 27 +----- .../Fixture/minimal.php.inc | 4 +- .../Source/Book.php | 11 --- 11 files changed, 86 insertions(+), 129 deletions(-) delete mode 100644 tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php diff --git a/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php b/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php index b02e689657e..521e867c30f 100644 --- a/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php +++ b/src/Bridge/Rector/Rules/AbstractApiResourceToResourceAttribute.php @@ -16,7 +16,7 @@ abstract class AbstractApiResourceToResourceAttribute extends AbstractRector protected array $operationTypes = ['collectionOperations', 'itemOperations']; - protected function formatOperations(array $operations): array + protected function normalizeOperations(array $operations): array { foreach ($operations as $name => $arguments) { /** diff --git a/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php index 3b753be7a7c..46aa79a0236 100644 --- a/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php +++ b/src/Bridge/Rector/Rules/ApiResourceAnnotationToResourceAttributeRector.php @@ -177,7 +177,7 @@ private function resolveOperations(DoctrineAnnotationTagValueNode $tagValue, Nod foreach ($this->operationTypes as $type) { if (isset($values[$type])) { - $operations = $this->formatOperations($values[$type]->getValuesWithExplicitSilentAndWithoutQuotes()); + $operations = $this->normalizeOperations($values[$type]->getValuesWithExplicitSilentAndWithoutQuotes()); foreach ($operations as $name => $arguments) { $node->attrGroups[] = $this->createOperationAttributeGroup($type, $name, $arguments); } diff --git a/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php b/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php index ef20d717b91..cd77e43acdb 100644 --- a/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php +++ b/src/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector.php @@ -7,6 +7,10 @@ use ApiPlatform\Core\Annotation\ApiResource; use ApiPlatform\Metadata\Resource; use PhpParser\Node; +use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Identifier; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\PhpAttribute\Printer\PhpAttributeGroupFactory; @@ -68,16 +72,16 @@ public function configure(array $configuration) : void */ public function refactor(Node $node) : ?Node { - $reflectionClass = new \ReflectionClass($node->name->getAttribute('className')); - - foreach ($reflectionClass->getAttributes() as $attribute) { - if (ApiResource::class !== $attribute->getName()) { - continue; + foreach ($node->attrGroups as $key => $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + if (!$this->isName($attribute->name, ApiResource::class)) { + continue; + } + $items = $this->createItemsFromArgs($attribute->args); + $arguments = $this->resolveOperations($items, $node); + $resourceAttributeGroup = $this->phpAttributeGroupFactory->createFromClassWithItems(Resource::class, $arguments); + array_unshift($node->attrGroups, $resourceAttributeGroup); } - - $arguments = $this->resolveOperations($attribute->getArguments(), $node); - $resourceAttributeGroup = $this->phpAttributeGroupFactory->createFromClassWithItems(Resource::class, $arguments); - array_unshift($node->attrGroups, $resourceAttributeGroup); } $this->cleanupAttrGroups($node); @@ -85,25 +89,51 @@ public function refactor(Node $node) : ?Node return $node; } + private function createItemsFromArgs(array $args) : array + { + $items = []; + + foreach ($args as $arg) { + $itemValue = $this->normalizeNodeValue($arg->value); + $itemName = $this->normalizeNodeValue($arg->name); + $items[$itemName] = $itemValue; + } + + return $items; + } + /** - * Remove initial ApiResource attribute from node - * - * @param Class_ $node + * @param mixed $value + * @return bool|float|int|string|array|Node\Expr */ - private function cleanupAttrGroups(Node $node) : void + private function normalizeNodeValue($value) { - if (false === $this->removeInitialAttribute) { - return; + if ($value instanceof ClassConstFetch) { + return sprintf('%s::%s', (string) $value->class, (string) $value->name); } - - foreach ($node->attrGroups as $key => $attrGroup) { - foreach ($attrGroup->attrs as $attribute) { - if ($this->isName($attribute->name, ApiResource::class)) { - unset($node->attrGroups[$key]); - continue(2); + if ($value instanceof Array_) { + return $this->normalizeNodeValue($value->items); + } + if ($value instanceof String_) { + return (string) $value->value; + } + if ($value instanceof Identifier) { + return $value->name; + } + if (\is_array($value)) { + $items = []; + foreach ($value as $itemKey => $itemValue) { + if (null === $itemValue->key) { + $items[] = $this->normalizeNodeValue($itemValue->value); + } else { + $items[$this->normalizeNodeValue($itemValue->key)] = $this->normalizeNodeValue($itemValue->value); } } + + return $items; } + + return $value; } /** @@ -113,7 +143,7 @@ private function resolveOperations(array $values, Node $node): array { foreach ($this->operationTypes as $type) { if (isset($values[$type])) { - $operations = $this->formatOperations($values[$type]); + $operations = $this->normalizeOperations($values[$type]); foreach ($operations as $name => $arguments) { $node->attrGroups[] = $this->createOperationAttributeGroup($type, $name, $arguments); } @@ -123,4 +153,25 @@ private function resolveOperations(array $values, Node $node): array return $values; } + + /** + * Remove initial ApiResource attribute from node + * + * @param Class_ $node + */ + private function cleanupAttrGroups(Node $node) : void + { + if (false === $this->removeInitialAttribute) { + return; + } + + foreach ($node->attrGroups as $key => $attrGroup) { + foreach ($attrGroup->attrs as $attribute) { + if ($this->isName($attribute->name, ApiResource::class)) { + unset($node->attrGroups[$key]); + continue(2); + } + } + } + } } diff --git a/src/Bridge/Rector/config/sets/attribute-to-resource-attribute.php b/src/Bridge/Rector/config/sets/attribute-to-resource-attribute.php index 45cf6dea194..fccd79c60cd 100644 --- a/src/Bridge/Rector/config/sets/attribute-to-resource-attribute.php +++ b/src/Bridge/Rector/config/sets/attribute-to-resource-attribute.php @@ -13,6 +13,8 @@ $parameters->set(Option::AUTO_IMPORT_NAMES, true); $services = $containerConfigurator->services(); - - $services->set(ApiResourceAttributeToResourceAttributeRector::class); + $services->set(ApiResourceAttributeToResourceAttributeRector::class) + ->call('configure', [[ + ApiResourceAttributeToResourceAttributeRector::REMOVE_INITIAL_ATTRIBUTE => true + ]]); }; diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc index 8aa3be9837f..317eee1bbdf 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/book.php.inc @@ -1,6 +1,6 @@ ['method' => 'GET', 'path' => 'custom_action_collection_dummies/{id}'], 'custom_normalization' => ['route_name' => 'custom_normalization', 'method' => 'GET'], 'short_custom_normalization' => ['route_name' => 'short_custom_normalization', 'method' => 'GET']], collectionOperations: ['get', 'get_custom' => ['method' => 'GET', 'path' => 'custom_action_collection_dummies'], 'custom_denormalization' => ['route_name' => 'custom_denormalization', 'method' => 'GET'], 'short_custom_denormalization' => ['route_name' => 'short_custom_denormalization', 'method' => 'GET']])] class CustomActionDummy { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc index 04e238fb793..c561cb7e268 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/disable_item_operation.php.inc @@ -5,20 +5,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Rector\Rules\ApiResourceAttributeToResou use ApiPlatform\Core\Action\NotFoundAction; use ApiPlatform\Core\Annotation\ApiResource; -/** - * @ApiResource( - * collectionOperations={ - * "get" - * }, - * itemOperations={ - * "get"={ - * "controller"=NotFoundAction::class, - * "read"=false, - * "output"=false - * } - * } - * ) - */ +#[ApiResource(collectionOperations: ['get'], itemOperations: ['get' => ['controller' => NotFoundAction::class, 'read' => false, 'output' => false]])] class DisableItemOperation { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc index e3dc563e389..57d76fcde04 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_custom_mutation.php.inc @@ -4,39 +4,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Rector\Rules\ApiResourceAttributeToResou use ApiPlatform\Core\Annotation\ApiResource; -/** - * @ApiResource(graphql={ - * "sum"={ - * "mutation"="app.graphql.mutation_resolver.dummy_custom", - * "normalization_context"={"groups"={"result"}}, - * "denormalization_context"={"groups"={"sum"}} - * }, - * "sumNotPersisted"={ - * "mutation"="app.graphql.mutation_resolver.dummy_custom_not_persisted", - * "normalization_context"={"groups"={"result"}}, - * "denormalization_context"={"groups"={"sum"}} - * }, - * "sumNoWriteCustomResult"={ - * "mutation"="app.graphql.mutation_resolver.dummy_custom_no_write_custom_result", - * "normalization_context"={"groups"={"result"}}, - * "denormalization_context"={"groups"={"sum"}}, - * "write"=false - * }, - * "sumOnlyPersist"={ - * "mutation"="app.graphql.mutation_resolver.dummy_custom_only_persist", - * "normalization_context"={"groups"={"result"}}, - * "denormalization_context"={"groups"={"sum"}}, - * "read"=false, - * "deserialize"=false, - * "validate"=false, - * "serialize"=false - * }, - * "testCustomArguments"={ - * "mutation"="app.graphql.mutation_resolver.dummy_custom", - * "args"={"operandC"={"type"="Int!"}} - * } - * }) - */ +#[ApiResource(graphql: ['sum' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNotPersisted' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_not_persisted', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']]], 'sumNoWriteCustomResult' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_no_write_custom_result', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'write' => false], 'sumOnlyPersist' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom_only_persist', 'normalization_context' => ['groups' => ['result']], 'denormalization_context' => ['groups' => ['sum']], 'read' => false, 'deserialize' => false, 'validate' => false, 'serialize' => false], 'testCustomArguments' => ['mutation' => 'app.graphql.mutation_resolver.dummy_custom', 'args' => ['operandC' => ['type' => 'Int!']]]])] class DummyCustomMutation { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc index 155d5e56a79..51341f6cac1 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/dummy_dto_no_input.php.inc @@ -7,32 +7,7 @@ use ApiPlatform\Core\Tests\Fixtures\TestBundle\Controller\DummyDtoNoInput\Create use ApiPlatform\Core\Tests\Fixtures\TestBundle\Controller\DummyDtoNoInput\DoubleBatAction; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\OutputDto; -/** - * @ApiResource( - * attributes={ - * "input"=false, - * "output"=OutputDto::class - * }, - * collectionOperations={ - * "post"={ - * "method"="POST", - * "path"="/dummy_dto_no_inputs", - * "controller"=CreateItemAction::class - * }, - * "get" - * }, - * itemOperations={ - * "get", - * "delete", - * "post_double_bat"={ - * "method"="POST", - * "path"="/dummy_dto_no_inputs/{id}/double_bat", - * "controller"=DoubleBatAction::class, - * "status"=200 - * } - * } - * ) - */ +#[ApiResource(attributes: ['input' => false, 'output' => OutputDto::class], collectionOperations: ['post' => ['method' => 'POST', 'path' => '/dummy_dto_no_inputs', 'controller' => CreateItemAction::class], 'get'], itemOperations: ['get', 'delete', 'post_double_bat' => ['method' => 'POST', 'path' => '/dummy_dto_no_inputs/{id}/double_bat', 'controller' => DoubleBatAction::class, 'status' => 200]])] class DummyDtoNoInput { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc index bf2040088bb..f34a4eb3e16 100644 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc +++ b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Fixture/minimal.php.inc @@ -4,9 +4,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Rector\Rules\ApiResourceAttributeToResou use ApiPlatform\Core\Annotation\ApiResource; -/** - * @ApiResource - */ +#[ApiResource] class Minimal { } diff --git a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php b/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php deleted file mode 100644 index 1beb49368e7..00000000000 --- a/tests/Bridge/Rector/Rules/ApiResourceAttributeToResourceAttributeRector/Source/Book.php +++ /dev/null @@ -1,11 +0,0 @@ - ['method' => 'GET', 'path' => '/books/by_isbn/{isbn}.{_format}', 'requirements' => ['isbn' => '.+'], 'identifiers' => 'isbn']])] -class Book -{ - -}