diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index f788356b..99b88c68 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -1,4 +1,29 @@ -# 19 Rules Overview +# 20 Rules Overview + +## AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector + +Adds `@return` PHPDoc type to Collection property getter by *ToMany attribute + +- class: [`Rector\Doctrine\CodeQuality\Rector\Class_\AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector`](../rules/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector.php) + +```diff + #[ORM\Entity] + final class Trainer + { + #[ORM\OneToMany(targetEntity:Training::class, mappedBy:"trainer")] + private $trainings; + ++ /** ++ * @return \Doctrine\Common\Collections\Collection ++ */ + public function getTrainings() + { + return $this->trainings; + } + } +``` + +
## ChangeCompositeExpressionAddMultipleWithWithRector diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRectorTest.php b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRectorTest.php new file mode 100644 index 00000000..79c608f4 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture/*'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/ManyToMany/getter_with_return_type.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/ManyToMany/getter_with_return_type.php.inc new file mode 100644 index 00000000..646da18d --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/ManyToMany/getter_with_return_type.php.inc @@ -0,0 +1,46 @@ +trainings; + } +} + +?> +----- + + */ + public function getTrainings(): Collection + { + return $this->trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_with_return_type.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_with_return_type.php.inc new file mode 100644 index 00000000..1365f967 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_with_return_type.php.inc @@ -0,0 +1,46 @@ +trainings; + } +} + +?> +----- + + */ + public function getTrainings(): Collection + { + return $this->trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_without_return_type.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_without_return_type.php.inc new file mode 100644 index 00000000..ce86311f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/getter_without_return_type.php.inc @@ -0,0 +1,46 @@ +trainings; + } +} + +?> +----- + + */ + public function getTrainings() + { + return $this->trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/multiple_returned_collection_properties.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/multiple_returned_collection_properties.php.inc new file mode 100644 index 00000000..93521ce3 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/multiple_returned_collection_properties.php.inc @@ -0,0 +1,57 @@ +morningTrainings; + } + + return $this->eveningTrainings; + } +} + +?> +----- +morningTrainings; + } + + return $this->eveningTrainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_a_collection_property.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_a_collection_property.php.inc new file mode 100644 index 00000000..7228819a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_a_collection_property.php.inc @@ -0,0 +1,41 @@ +trainings; + } +} + +?> +----- +trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_entity_class.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_entity_class.php.inc new file mode 100644 index 00000000..4755cf63 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/not_entity_class.php.inc @@ -0,0 +1,41 @@ +trainings; + } +} + +?> +----- +trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/with_invalid_return_type.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/with_invalid_return_type.php.inc new file mode 100644 index 00000000..5e78059a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Fixture/OneToMany/with_invalid_return_type.php.inc @@ -0,0 +1,47 @@ + + */ + public function getTrainings() + { + return $this->trainings; + } +} + +?> +----- + + */ + public function getTrainings() + { + return $this->trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Source/Training.php b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Source/Training.php new file mode 100644 index 00000000..4438c859 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector/Source/Training.php @@ -0,0 +1,10 @@ +rule(AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector::class); +}; diff --git a/rules/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector.php b/rules/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector.php new file mode 100644 index 00000000..de967aa7 --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/AddReturnDocBlockToCollectionPropertyGetterByToManyAttributeRector.php @@ -0,0 +1,158 @@ +trainings; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +#[ORM\Entity] +final class Trainer +{ + #[ORM\OneToMany(targetEntity:Training::class, mappedBy:"trainer")] + private $trainings; + + /** + * @return \Doctrine\Common\Collections\Collection + */ + public function getTrainings() + { + return $this->trainings; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactorWithScope(Node $node, Scope $scope): ?Node + { + if (! $this->isDoctrineEntityClass($node)) { + return null; + } + + foreach ($node->getMethods() as $classMethod) { + if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($classMethod, $scope)) { + return null; + } + + $property = $this->methodUniqueReturnedPropertyResolver->resolve($node, $classMethod); + + if ($property === null) { + continue; + } + + $collectionObjectType = $this->getCollectionObjectTypeFromToManyAttribute($property); + + if ($collectionObjectType === null) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + $newVarType = $this->collectionTypeFactory->createType($collectionObjectType); + $this->phpDocTypeChanger->changeReturnType($classMethod, $phpDocInfo, $newVarType); + } + + return $node; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::ATTRIBUTES; + } + + private function getCollectionObjectTypeFromToManyAttribute(Property $property): ?FullyQualifiedObjectType + { + $targetEntityExpr = $this->attributeFinder->findAttributeByClassesArgByName( + $property, + ToManyMappings::TO_MANY_CLASSES, + 'targetEntity' + ); + + if (! $targetEntityExpr instanceof ClassConstFetch) { + return null; + } + + $targetEntityClassName = $this->targetEntityResolver->resolveFromExpr($targetEntityExpr); + + if ($targetEntityClassName === null) { + return null; + } + + return new FullyQualifiedObjectType($targetEntityClassName); + } + + private function isDoctrineEntityClass(Class_ $class): bool + { + $entityAttribute = $this->attributeFinder->findAttributeByClasses( + $class, + ['Doctrine\ORM\Mapping\Entity', 'Doctrine\ORM\Mapping\Embeddable'], + ); + + return $entityAttribute !== null; + } +} diff --git a/src/NodeAnalyzer/MethodUniqueReturnedPropertyResolver.php b/src/NodeAnalyzer/MethodUniqueReturnedPropertyResolver.php new file mode 100644 index 00000000..1838a24d --- /dev/null +++ b/src/NodeAnalyzer/MethodUniqueReturnedPropertyResolver.php @@ -0,0 +1,43 @@ +betterNodeFinder->findInstancesOfInFunctionLikeScoped($classMethod, Return_::class); + + if (\count($returns) !== 1) { + return null; + } + + $return = \reset($returns); + + $returnExpr = $return->expr; + + if (! $returnExpr instanceof PropertyFetch) { + return null; + } + + $propertyName = (string) $this->nodeNameResolver->getName($returnExpr); + + return $class->getProperty($propertyName); + } +}