From cec46f77f4632642b3c404fce4fb381b95ce8250 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 00:32:53 +0200 Subject: [PATCH 1/8] add fixutre for var and attribute --- .../OneToMany/no_var_attribute.php.inc | 2 - .../OneToMany/var_and_attribute.php.inc | 52 +++++++++++++++++++ .../config/configured_rule.php | 6 +-- 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc index 1b2e7a79..3a74522a 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc @@ -2,7 +2,6 @@ namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture\OneToMany; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training; @@ -19,7 +18,6 @@ class NoVar namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture\OneToMany; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training; diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc new file mode 100644 index 00000000..d8b12065 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc @@ -0,0 +1,52 @@ +trainings = $trainings; + } +} + +?> +----- + + */ + #[ORM\OneToMany(targetEntity:Training::class, mappedBy:"trainer")] + private $trainings = []; + + /** + * @param Collection|\Training[] $trainings + */ + public function setTrainings($trainings) + { + $this->trainings = $trainings; + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php index bd39405a..82715af5 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php @@ -5,6 +5,6 @@ use Rector\Config\RectorConfig; use Rector\Doctrine\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rule(ImproveDoctrineCollectionDocTypeInEntityRector::class); -}; +return RectorConfig::configure() + ->withRules([ImproveDoctrineCollectionDocTypeInEntityRector::class]) + ->withPhpVersion(\Rector\ValueObject\PhpVersionFeature::ATTRIBUTES); From aacc447d995c51a0e196f1e98af5584ae543db96 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 00:51:28 +0200 Subject: [PATCH 2/8] Fix attribute support in ImproveDoctrineCollectionDocTypeInEntityRector --- .../OneToMany/var_and_attribute.php.inc | 5 +---- .../config/configured_rule.php | 3 +-- ...octrineCollectionDocTypeInEntityRector.php | 22 ++++++++++++++----- src/NodeAnalyzer/TargetEntityResolver.php | 19 ++++++++++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc index d8b12065..85cb54a6 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/var_and_attribute.php.inc @@ -35,14 +35,11 @@ use Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionD class VarAndAttribute { /** - * @var Collection + * @var \Doctrine\Common\Collections\Collection */ #[ORM\OneToMany(targetEntity:Training::class, mappedBy:"trainer")] private $trainings = []; - /** - * @param Collection|\Training[] $trainings - */ public function setTrainings($trainings) { $this->trainings = $trainings; diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php index 82715af5..1ae2bc3e 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/config/configured_rule.php @@ -6,5 +6,4 @@ use Rector\Doctrine\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector; return RectorConfig::configure() - ->withRules([ImproveDoctrineCollectionDocTypeInEntityRector::class]) - ->withPhpVersion(\Rector\ValueObject\PhpVersionFeature::ATTRIBUTES); + ->withRules([ImproveDoctrineCollectionDocTypeInEntityRector::class]); diff --git a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php index fc71d0b0..298a9e3f 100644 --- a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php +++ b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php @@ -5,6 +5,7 @@ namespace Rector\Doctrine\CodeQuality\Rector\Property; use PhpParser\Node; +use PhpParser\Node\Attribute; use PhpParser\Node\Expr; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; @@ -126,6 +127,7 @@ private function refactorProperty(Property $property): ?Property CollectionMapping::TO_MANY_CLASSES, 'targetEntity' ); + if (! $targetEntityExpr instanceof Expr) { return null; } @@ -240,14 +242,24 @@ private function refactorPropertyPhpDocInfo(Property $property, PhpDocInfo $phpD private function refactorAttribute(Expr $expr, PhpDocInfo $phpDocInfo, Property $property): ?Property { - $phpDocVarTagValueNode = $phpDocInfo->getVarTagValueNode(); - $phpDocCollectionVarTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property); - if ($phpDocVarTagValueNode instanceof VarTagValueNode && ! $phpDocCollectionVarTagValueNode instanceof VarTagValueNode) { - return null; + $toManyAttribute = $this->attributeFinder->findAttributeByClasses( + $property, + CollectionMapping::TO_MANY_CLASSES + ); + if ($toManyAttribute instanceof Attribute) { + $targetEntityClassName = $this->targetEntityResolver->resolveFromAttribute($toManyAttribute); + } else { + $phpDocVarTagValueNode = $phpDocInfo->getVarTagValueNode(); + $phpDocCollectionVarTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property); + + if ($phpDocVarTagValueNode instanceof VarTagValueNode && ! $phpDocCollectionVarTagValueNode instanceof VarTagValueNode) { + return null; + } + + $targetEntityClassName = $this->targetEntityResolver->resolveFromExpr($expr); } - $targetEntityClassName = $this->targetEntityResolver->resolveFromExpr($expr); if ($targetEntityClassName === null) { return null; } diff --git a/src/NodeAnalyzer/TargetEntityResolver.php b/src/NodeAnalyzer/TargetEntityResolver.php index a7120da5..ef7c0f4c 100644 --- a/src/NodeAnalyzer/TargetEntityResolver.php +++ b/src/NodeAnalyzer/TargetEntityResolver.php @@ -4,8 +4,10 @@ namespace Rector\Doctrine\NodeAnalyzer; +use PhpParser\Node\Attribute; use PhpParser\Node\Expr; use PhpParser\Node\Expr\ClassConstFetch; +use PhpParser\Node\Identifier; use PhpParser\Node\Scalar\String_; use PHPStan\Reflection\ReflectionProvider; use Rector\Exception\NotImplementedYetException; @@ -19,6 +21,23 @@ public function __construct( ) { } + public function resolveFromAttribute(Attribute $attribute): ?string + { + foreach ($attribute->args as $arg) { + if (! $arg->name instanceof Identifier) { + continue; + } + + if ($arg->name->toString() !== 'targetEntity') { + continue; + } + + return $this->resolveFromExpr($arg->value); + } + + return null; + } + public function resolveFromExpr(Expr $targetEntityExpr): string|null { if ($targetEntityExpr instanceof ClassConstFetch) { From 2ffe7f680f68c9d10e5c76f1c980944f438684ff Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 13:25:07 +0200 Subject: [PATCH 3/8] move attribute to own directory --- .../OneToMany/{ => Attribute}/no_var_attribute.php.inc | 4 ++-- .../{ => Attribute}/skip_collection_generic_class.php.inc | 2 +- .../{ => Attribute}/skip_collection_generic_object.php.inc | 2 +- .../OneToMany/{ => Attribute}/var_and_attribute.php.inc | 7 +++++-- 4 files changed, 9 insertions(+), 6 deletions(-) rename rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/{ => Attribute}/no_var_attribute.php.inc (86%) rename rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/{ => Attribute}/skip_collection_generic_class.php.inc (89%) rename rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/{ => Attribute}/skip_collection_generic_object.php.inc (88%) rename rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/{ => Attribute}/var_and_attribute.php.inc (78%) diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/no_var_attribute.php.inc similarity index 86% rename from rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc rename to rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/no_var_attribute.php.inc index 3a74522a..42d06889 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/no_var_attribute.php.inc +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/no_var_attribute.php.inc @@ -1,6 +1,6 @@ $trainings + */ public function setTrainings($trainings) { $this->trainings = $trainings; From 3412e1f57448d1fe88da319f60c2b7d6940f53d6 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 13:32:14 +0200 Subject: [PATCH 4/8] extract SetterCollectionResolver --- ...octrineCollectionDocTypeInEntityRector.php | 41 +------------- .../CodeQuality/SetterCollectionResolver.php | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 rules/CodeQuality/SetterCollectionResolver.php diff --git a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php index 298a9e3f..10582d8c 100644 --- a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php +++ b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php @@ -8,25 +8,21 @@ use PhpParser\Node\Attribute; use PhpParser\Node\Expr; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\Doctrine\CodeQuality\Enum\CollectionMapping; +use Rector\Doctrine\CodeQuality\SetterCollectionResolver; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver; use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver; use Rector\Doctrine\TypeAnalyzer\CollectionTypeFactory; use Rector\Doctrine\TypeAnalyzer\CollectionTypeResolver; use Rector\Doctrine\TypeAnalyzer\CollectionVarTagValueNodeResolver; -use Rector\NodeManipulator\AssignManipulator; use Rector\Rector\AbstractRector; -use Rector\Reflection\ReflectionResolver; -use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -38,16 +34,14 @@ final class ImproveDoctrineCollectionDocTypeInEntityRector extends AbstractRecto { public function __construct( private readonly CollectionTypeFactory $collectionTypeFactory, - private readonly AssignManipulator $assignManipulator, private readonly CollectionTypeResolver $collectionTypeResolver, private readonly CollectionVarTagValueNodeResolver $collectionVarTagValueNodeResolver, private readonly PhpDocTypeChanger $phpDocTypeChanger, private readonly DoctrineDocBlockResolver $doctrineDocBlockResolver, - private readonly ReflectionResolver $reflectionResolver, private readonly AttributeFinder $attributeFinder, private readonly TargetEntityResolver $targetEntityResolver, private readonly PhpDocInfoFactory $phpDocInfoFactory, - private readonly StaticTypeMapper $staticTypeMapper + private readonly SetterCollectionResolver $setterCollectionResolver, ) { } @@ -147,7 +141,7 @@ private function refactorClassMethod(Class_ $class): ?Class_ continue; } - $collectionObjectType = $this->resolveCollectionSetterAssignType($class, $classMethod); + $collectionObjectType = $this->setterCollectionResolver->resolveAssignedType($class, $classMethod); if (! $collectionObjectType instanceof Type) { continue; } @@ -183,35 +177,6 @@ private function refactorClassMethod(Class_ $class): ?Class_ return null; } - private function resolveCollectionSetterAssignType(Class_ $class, ClassMethod $classMethod): ?Type - { - $propertyFetches = $this->assignManipulator->resolveAssignsToLocalPropertyFetches($classMethod); - if (count($propertyFetches) !== 1) { - return null; - } - - $phpPropertyReflection = $this->reflectionResolver->resolvePropertyReflectionFromPropertyFetch( - $propertyFetches[0] - ); - if (! $phpPropertyReflection instanceof PhpPropertyReflection) { - return null; - } - - $propertyName = (string) $this->nodeNameResolver->getName($propertyFetches[0]); - $property = $class->getProperty($propertyName); - - if (! $property instanceof Property) { - return null; - } - - $varTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property); - if (! $varTagValueNode instanceof VarTagValueNode) { - return null; - } - - return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property); - } - private function refactorPropertyPhpDocInfo(Property $property, PhpDocInfo $phpDocInfo): ?Property { $varTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property); diff --git a/rules/CodeQuality/SetterCollectionResolver.php b/rules/CodeQuality/SetterCollectionResolver.php new file mode 100644 index 00000000..b5583941 --- /dev/null +++ b/rules/CodeQuality/SetterCollectionResolver.php @@ -0,0 +1,56 @@ +assignManipulator->resolveAssignsToLocalPropertyFetches($classMethod); + if (count($propertyFetches) !== 1) { + return null; + } + + $phpPropertyReflection = $this->reflectionResolver->resolvePropertyReflectionFromPropertyFetch( + $propertyFetches[0] + ); + if (! $phpPropertyReflection instanceof PhpPropertyReflection) { + return null; + } + + $propertyName = (string) $this->nodeNameResolver->getName($propertyFetches[0]); + $property = $class->getProperty($propertyName); + + if (! $property instanceof Property) { + return null; + } + + $varTagValueNode = $this->collectionVarTagValueNodeResolver->resolve($property); + if (! $varTagValueNode instanceof VarTagValueNode) { + return null; + } + + return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property); + } +} From b531e2b9d98af81bc3a7b2045af5893960d048dc Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 13:36:51 +0200 Subject: [PATCH 5/8] misc --- .../Attribute/var_and_attribute.php.inc | 2 +- src/NodeAnalyzer/AttributeFinder.php | 10 ++++++++++ .../CollectionVarTagValueNodeResolver.php | 16 ++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc index c97d443b..08fdb79c 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc @@ -41,7 +41,7 @@ class VarAndAttribute private $trainings = []; /** - * @param \Doctrine\Common\Collections\Collection $trainings + * @param Collection|\Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training[] $trainings */ public function setTrainings($trainings) { diff --git a/src/NodeAnalyzer/AttributeFinder.php b/src/NodeAnalyzer/AttributeFinder.php index 4d6c9aec..0f761789 100644 --- a/src/NodeAnalyzer/AttributeFinder.php +++ b/src/NodeAnalyzer/AttributeFinder.php @@ -114,6 +114,16 @@ public function findAttributeByClasses( return null; } + /** + * @param string[] $attributeClasses + */ + public function hasAttributeByClasses( + ClassMethod | Property | ClassLike | Param $node, + array $attributeClasses + ): bool { + return $this->findAttributeByClasses($node, $attributeClasses) !== []; + } + private function findArgByName(Attribute $attribute, string $argName): Expr|null { foreach ($attribute->args as $arg) { diff --git a/src/TypeAnalyzer/CollectionVarTagValueNodeResolver.php b/src/TypeAnalyzer/CollectionVarTagValueNodeResolver.php index 5fc21443..b0f874de 100644 --- a/src/TypeAnalyzer/CollectionVarTagValueNodeResolver.php +++ b/src/TypeAnalyzer/CollectionVarTagValueNodeResolver.php @@ -6,23 +6,35 @@ use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Doctrine\CodeQuality\Enum\CollectionMapping; +use Rector\Doctrine\NodeAnalyzer\AttributeFinder; final readonly class CollectionVarTagValueNodeResolver { public function __construct( - private PhpDocInfoFactory $phpDocInfoFactory + private PhpDocInfoFactory $phpDocInfoFactory, + private AttributeFinder $attributeFinder, ) { } public function resolve(Property $property): ?VarTagValueNode { $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); - if (! $phpDocInfo->hasByAnnotationClasses(CollectionMapping::TO_MANY_CLASSES)) { + if (! $this->hasAnnotationOrAttributeToMany($phpDocInfo, $property)) { return null; } return $phpDocInfo->getVarTagValueNode(); } + + private function hasAnnotationOrAttributeToMany(PhpDocInfo $phpDocInfo, Property $property): bool + { + if ($phpDocInfo->hasByAnnotationClasses(CollectionMapping::TO_MANY_CLASSES)) { + return true; + } + + return $this->attributeFinder->hasAttributeByClasses($property, CollectionMapping::TO_MANY_CLASSES); + } } From cf948f456aaf9cc6b758b57fe5bb2174359a3e30 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 13:56:50 +0200 Subject: [PATCH 6/8] add full generic collection type --- .../Attribute/var_and_attribute.php.inc | 2 +- .../OneToManyAttributeTransformer.php | 2 +- ...octrineCollectionDocTypeInEntityRector.php | 13 +++-- .../CodeQuality/SetterCollectionResolver.php | 55 ++++++++++++++++++- src/NodeAnalyzer/TargetEntityResolver.php | 3 +- src/TypeAnalyzer/CollectionTypeFactory.php | 6 +- 6 files changed, 69 insertions(+), 12 deletions(-) diff --git a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc index 08fdb79c..c97d443b 100644 --- a/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc +++ b/rules-tests/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector/Fixture/OneToMany/Attribute/var_and_attribute.php.inc @@ -41,7 +41,7 @@ class VarAndAttribute private $trainings = []; /** - * @param Collection|\Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\Training[] $trainings + * @param \Doctrine\Common\Collections\Collection $trainings */ public function setTrainings($trainings) { diff --git a/rules/CodeQuality/AttributeTransformer/PropertyAttributeTransformer/OneToManyAttributeTransformer.php b/rules/CodeQuality/AttributeTransformer/PropertyAttributeTransformer/OneToManyAttributeTransformer.php index bd180332..512448b2 100644 --- a/rules/CodeQuality/AttributeTransformer/PropertyAttributeTransformer/OneToManyAttributeTransformer.php +++ b/rules/CodeQuality/AttributeTransformer/PropertyAttributeTransformer/OneToManyAttributeTransformer.php @@ -32,7 +32,7 @@ public function transform(EntityMapping $entityMapping, Property|Param $property unset($oneToManyMapping[EntityMappingKey::ORDER_BY]); $args = $this->nodeFactory->createArgs($oneToManyMapping); - NodeValueNormalizer::ensureKeyIsClassConstFetch($args, 'targetEntity'); + NodeValueNormalizer::ensureKeyIsClassConstFetch($args, EntityMappingKey::TARGET_ENTITY); $property->attrGroups[] = AttributeFactory::createGroup($this->getClassName(), $args); } diff --git a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php index 10582d8c..cd842119 100644 --- a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php +++ b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php @@ -10,11 +10,12 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; -use PHPStan\Type\Type; +use PHPStan\Type\Generic\GenericObjectType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\Doctrine\CodeQuality\Enum\CollectionMapping; +use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey; use Rector\Doctrine\CodeQuality\SetterCollectionResolver; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver; @@ -119,7 +120,7 @@ private function refactorProperty(Property $property): ?Property $targetEntityExpr = $this->attributeFinder->findAttributeByClassesArgByName( $property, CollectionMapping::TO_MANY_CLASSES, - 'targetEntity' + EntityMappingKey::TARGET_ENTITY ); if (! $targetEntityExpr instanceof Expr) { @@ -141,8 +142,11 @@ private function refactorClassMethod(Class_ $class): ?Class_ continue; } - $collectionObjectType = $this->setterCollectionResolver->resolveAssignedType($class, $classMethod); - if (! $collectionObjectType instanceof Type) { + $collectionObjectType = $this->setterCollectionResolver->resolveAssignedGenericCollectionType( + $class, + $classMethod + ); + if (! $collectionObjectType instanceof GenericObjectType) { continue; } @@ -159,6 +163,7 @@ private function refactorClassMethod(Class_ $class): ?Class_ /** @var string $parameterName */ $parameterName = $this->getName($param); + $this->phpDocTypeChanger->changeParamType( $classMethod, $phpDocInfo, diff --git a/rules/CodeQuality/SetterCollectionResolver.php b/rules/CodeQuality/SetterCollectionResolver.php index b5583941..8e83259b 100644 --- a/rules/CodeQuality/SetterCollectionResolver.php +++ b/rules/CodeQuality/SetterCollectionResolver.php @@ -1,5 +1,7 @@ assignManipulator->resolveAssignsToLocalPropertyFetches($classMethod); if (count($propertyFetches) !== 1) { @@ -51,6 +65,43 @@ public function resolveAssignedType(Class_ $class, ClassMethod $classMethod): ?T return null; } - return $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($varTagValueNode->type, $property); + // remove collection union type, so this can be turned into generic type + $resolvedType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType( + $varTagValueNode->type, + $property + ); + + if ($resolvedType instanceof UnionType) { + $nonCollectionTypes = []; + foreach ($resolvedType->getTypes() as $unionedType) { + if (! $this->isCollectionType($unionedType)) { + continue; + } + + $nonCollectionTypes[] = $unionedType; + } + + if (count($nonCollectionTypes) === 1) { + $soleType = $nonCollectionTypes[0]; + if ($soleType instanceof ArrayType && $soleType->getItemType() instanceof ObjectType) { + return $this->collectionTypeFactory->createType($soleType->getItemType()); + } + } + } + + if ($resolvedType instanceof GenericObjectType) { + return $resolvedType; + } + + return null; + } + + private function isCollectionType(Type $type): bool + { + if ($type instanceof ShortenedObjectType && $type->getFullyQualifiedName() === self::COLLECTION_CLASS) { + return true; + } + + return $type instanceof ObjectType && $type->getClassName() === self::COLLECTION_CLASS; } } diff --git a/src/NodeAnalyzer/TargetEntityResolver.php b/src/NodeAnalyzer/TargetEntityResolver.php index ef7c0f4c..da1cd5b2 100644 --- a/src/NodeAnalyzer/TargetEntityResolver.php +++ b/src/NodeAnalyzer/TargetEntityResolver.php @@ -10,6 +10,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Scalar\String_; use PHPStan\Reflection\ReflectionProvider; +use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey; use Rector\Exception\NotImplementedYetException; use Rector\NodeNameResolver\NodeNameResolver; @@ -28,7 +29,7 @@ public function resolveFromAttribute(Attribute $attribute): ?string continue; } - if ($arg->name->toString() !== 'targetEntity') { + if ($arg->name->toString() !== EntityMappingKey::TARGET_ENTITY) { continue; } diff --git a/src/TypeAnalyzer/CollectionTypeFactory.php b/src/TypeAnalyzer/CollectionTypeFactory.php index aa1adf99..de1cd1d9 100644 --- a/src/TypeAnalyzer/CollectionTypeFactory.php +++ b/src/TypeAnalyzer/CollectionTypeFactory.php @@ -6,13 +6,13 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\IntegerType; -use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; +use PHPStan\Type\ObjectType; final class CollectionTypeFactory { - public function createType(FullyQualifiedObjectType $fullyQualifiedObjectType): GenericObjectType + public function createType(ObjectType $objectType): GenericObjectType { - $genericTypes = [new IntegerType(), $fullyQualifiedObjectType]; + $genericTypes = [new IntegerType(), $objectType]; return new GenericObjectType('Doctrine\Common\Collections\Collection', $genericTypes); } } From ea1077c8199e7483cd309162929b5e7ca15616a5 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 14:02:44 +0200 Subject: [PATCH 7/8] improve sample to note setter as well --- ...proveDoctrineCollectionDocTypeInEntityRector.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php index cd842119..9c1330ef 100644 --- a/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php +++ b/rules/CodeQuality/Rector/Property/ImproveDoctrineCollectionDocTypeInEntityRector.php @@ -66,6 +66,11 @@ class SomeClass * @var Collection|Trainer[] */ private $trainings = []; + + public function setTrainings($trainings) + { + $this->trainings = $trainings; + } } CODE_SAMPLE , @@ -83,6 +88,14 @@ class SomeClass * @var Collection */ private $trainings = []; + + /** + * @param Collection $trainings + */ + public function setTrainings($trainings) + { + $this->trainings = $trainings; + } } CODE_SAMPLE ), From bf7589e2c21e8ca5b28c9eda9868ece7b7f95266 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 15 Aug 2024 14:05:59 +0200 Subject: [PATCH 8/8] extract DoctrineClass enum --- rules/CodeQuality/Enum/DoctrineClass.php | 13 +++++++++++++ rules/CodeQuality/SetterCollectionResolver.php | 10 +++------- .../ToManyRelationPropertyTypeResolver.php | 8 ++------ 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 rules/CodeQuality/Enum/DoctrineClass.php diff --git a/rules/CodeQuality/Enum/DoctrineClass.php b/rules/CodeQuality/Enum/DoctrineClass.php new file mode 100644 index 00000000..5e746987 --- /dev/null +++ b/rules/CodeQuality/Enum/DoctrineClass.php @@ -0,0 +1,13 @@ +getFullyQualifiedName() === self::COLLECTION_CLASS) { + if ($type instanceof ShortenedObjectType && $type->getFullyQualifiedName() === DoctrineClass::COLLECTION) { return true; } - return $type instanceof ObjectType && $type->getClassName() === self::COLLECTION_CLASS; + return $type instanceof ObjectType && $type->getClassName() === DoctrineClass::COLLECTION; } } diff --git a/src/NodeManipulator/ToManyRelationPropertyTypeResolver.php b/src/NodeManipulator/ToManyRelationPropertyTypeResolver.php index 3e2bbcf3..35c7da3c 100644 --- a/src/NodeManipulator/ToManyRelationPropertyTypeResolver.php +++ b/src/NodeManipulator/ToManyRelationPropertyTypeResolver.php @@ -13,6 +13,7 @@ use Rector\BetterPhpDocParser\PhpDoc\StringNode; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Doctrine\CodeQuality\Enum\CollectionMapping; +use Rector\Doctrine\CodeQuality\Enum\DoctrineClass; use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey; use Rector\Doctrine\CodeQuality\Enum\OdmMappingKey; use Rector\Doctrine\NodeAnalyzer\AttributeFinder; @@ -23,11 +24,6 @@ final readonly class ToManyRelationPropertyTypeResolver { - /** - * @var string - */ - private const COLLECTION_TYPE = 'Doctrine\Common\Collections\Collection'; - public function __construct( private PhpDocInfoFactory $phpDocInfoFactory, private ShortClassExpander $shortClassExpander, @@ -90,7 +86,7 @@ private function resolveTypeFromTargetEntity(Expr|string $targetEntity, Property } if (! is_string($targetEntity)) { - return new FullyQualifiedObjectType(self::COLLECTION_TYPE); + return new FullyQualifiedObjectType(DoctrineClass::COLLECTION); } $entityFullyQualifiedClass = $this->shortClassExpander->resolveFqnTargetEntity($targetEntity, $property);