Skip to content

Commit

Permalink
Add ODM support to TypedPropertyFromToManyRelationTypeRector (#335)
Browse files Browse the repository at this point in the history
* add fixture

* Add ODM support to TypedPropertyFromToManyRelationTypeRector

* make use of enums
  • Loading branch information
TomasVotruba authored Jul 25, 2024
1 parent 2a05a53 commit 3ba7adb
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 60 deletions.
2 changes: 2 additions & 0 deletions config/sets/typed-collections.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
use Rector\Doctrine\CodeQuality\Rector\Class_\AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector;
use Rector\Doctrine\CodeQuality\Rector\Class_\ExplicitRelationCollectionRector;
use Rector\Doctrine\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector;
use Rector\Doctrine\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector;

return RectorConfig::configure()
->withRules([
ExplicitRelationCollectionRector::class,
AddReturnDocBlockToCollectionPropertyGetterByToManyAnnotationRector::class,
TypedPropertyFromToManyRelationTypeRector::class,
ImproveDoctrineCollectionDocTypeInEntityRector::class,
]);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector\Fixture;

use Doctrine\Common\Collections\ArrayCollection;
use Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector\Source\TrainingTerm;

/**
* @\Doctrine\ODM\MongoDB\Mapping\Annotations\Document
*/
class OdmToMany
{
/**
* @\Doctrine\ODM\MongoDB\Mapping\Annotations\ReferenceMany(targetDocument="TrainingTerm")
*/
private $products;

public function __construct()
{
$this->products = new ArrayCollection();
}
}

?>
-----
<?php

namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector\Fixture;

use Doctrine\Common\Collections\ArrayCollection;
use Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector\Source\TrainingTerm;

/**
* @\Doctrine\ODM\MongoDB\Mapping\Annotations\Document
*/
class OdmToMany
{
/**
* @\Doctrine\ODM\MongoDB\Mapping\Annotations\ReferenceMany(targetDocument="TrainingTerm")
* @var \Doctrine\Common\Collections\Collection<int, \Rector\Doctrine\Tests\CodeQuality\Rector\Property\TypedPropertyFromToManyRelationTypeRector\Source\TrainingTerm>
*/
private \Doctrine\Common\Collections\Collection $products;

public function __construct()
{
$this->products = new ArrayCollection();
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
use Rector\Doctrine\Enum\MappingClass;
use Rector\Doctrine\Enum\OdmMappingClass;

class ToManyMappings
final class CollectionMapping
{
/**
* @var string[]
*/
final public const TO_MANY_CLASSES = [
public const TO_MANY_CLASSES = [
MappingClass::ONE_TO_MANY,
MappingClass::MANY_TO_MANY,
OdmMappingClass::REFERENCE_MANY,
];

/**
* @var string[]
*/
public const TO_ONE_CLASSES = [MappingClass::MANY_TO_ONE, MappingClass::ONE_TO_ONE];
}
16 changes: 16 additions & 0 deletions rules/CodeQuality/Enum/OdmMappingKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Rector\Doctrine\CodeQuality\Enum;

/**
* @api
*/
final class OdmMappingKey
{
/**
* @var string
*/
public const TARGET_DOCUMENT = 'targetDocument';
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Doctrine\CodeQuality\Enum\ToManyMappings;
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver;
use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver;
Expand Down Expand Up @@ -117,13 +117,13 @@ public function refactor(Node $node): ?Node
private function refactorProperty(Property $property): ?Property
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
if ($phpDocInfo->hasByAnnotationClasses(ToManyMappings::TO_MANY_CLASSES)) {
if ($phpDocInfo->hasByAnnotationClasses(CollectionMapping::TO_MANY_CLASSES)) {
return $this->refactorPropertyPhpDocInfo($property, $phpDocInfo);
}

$targetEntityExpr = $this->attributeFinder->findAttributeByClassesArgByName(
$property,
ToManyMappings::TO_MANY_CLASSES,
CollectionMapping::TO_MANY_CLASSES,
'targetEntity'
);
if (! $targetEntityExpr instanceof Expr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ public function refactor(Node $node): Property|null
}

$propertyType = $this->toManyRelationPropertyTypeResolver->resolve($node);

if (! $propertyType instanceof Type || $propertyType instanceof MixedType) {
return null;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Enum/MappingClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ final class MappingClass
*/
public const ONE_TO_MANY = 'Doctrine\ORM\Mapping\OneToMany';

/**
* @var string
*/
public const ONE_TO_ONE = 'Doctrine\ORM\Mapping\OneToOne';

/**
* @var string
*/
Expand Down
24 changes: 24 additions & 0 deletions src/NodeAnalyzer/AttributeFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ public function findAttributeByClassArgByName(
return $this->findAttributeByClassesArgByName($node, [$attributeClass], $argName);
}

/**
* @param string[] $attributeClasses
* @param string[] $argNames
*/
public function findAttributeByClassesArgByNames(
ClassMethod | Property | ClassLike | Param $node,
array $attributeClasses,
array $argNames
): ?Expr {
$attribute = $this->findAttributeByClasses($node, $attributeClasses);
if (! $attribute instanceof Attribute) {
return null;
}

foreach ($argNames as $argName) {
$argExpr = $this->findArgByName($attribute, $argName);
if ($argExpr instanceof Expr) {
return $argExpr;
}
}

return null;
}

/**
* @param string[] $attributeClasses
*/
Expand Down
23 changes: 10 additions & 13 deletions src/NodeManipulator/ToManyRelationPropertyTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDoc\StringNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;
use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey;
use Rector\Doctrine\CodeQuality\Enum\OdmMappingKey;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\Doctrine\PhpDoc\ShortClassExpander;
use Rector\Doctrine\TypeAnalyzer\CollectionTypeFactory;
Expand All @@ -25,14 +28,6 @@
*/
private const COLLECTION_TYPE = 'Doctrine\Common\Collections\Collection';

/**
* @var class-string[]
*/
private const TO_MANY_ANNOTATION_CLASSES = [
'Doctrine\ORM\Mapping\OneToMany',
'Doctrine\ORM\Mapping\ManyToMany',
];

public function __construct(
private PhpDocInfoFactory $phpDocInfoFactory,
private ShortClassExpander $shortClassExpander,
Expand All @@ -46,15 +41,15 @@ public function resolve(Property $property): ?Type
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);

$doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClasses(self::TO_MANY_ANNOTATION_CLASSES);
$doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClasses(CollectionMapping::TO_MANY_CLASSES);
if ($doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
return $this->processToManyRelation($property, $doctrineAnnotationTagValueNode);
}

$expr = $this->attributeFinder->findAttributeByClassesArgByName(
$expr = $this->attributeFinder->findAttributeByClassesArgByNames(
$property,
self::TO_MANY_ANNOTATION_CLASSES,
'targetEntity'
CollectionMapping::TO_MANY_CLASSES,
[EntityMappingKey::TARGET_ENTITY, OdmMappingKey::TARGET_DOCUMENT]
);

if (! $expr instanceof Expr) {
Expand All @@ -68,7 +63,9 @@ private function processToManyRelation(
Property $property,
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode
): Type|null {
$targetEntityArrayItemNode = $doctrineAnnotationTagValueNode->getValue('targetEntity');
$targetEntityArrayItemNode = $doctrineAnnotationTagValueNode->getValue(
EntityMappingKey::TARGET_ENTITY
) ?: $doctrineAnnotationTagValueNode->getValue(OdmMappingKey::TARGET_DOCUMENT);
if (! $targetEntityArrayItemNode instanceof ArrayItemNode) {
return null;
}
Expand Down
15 changes: 6 additions & 9 deletions src/NodeManipulator/ToOneRelationPropertyTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,15 @@
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocParser\ClassAnnotationMatcher;
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;
use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey;
use Rector\Doctrine\NodeAnalyzer\AttributeFinder;
use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver;
use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;

final readonly class ToOneRelationPropertyTypeResolver
{
/**
* @var class-string[]
*/
private const TO_ONE_ANNOTATION_CLASSES = ['Doctrine\ORM\Mapping\ManyToOne', 'Doctrine\ORM\Mapping\OneToOne'];

private const JOIN_COLUMN = ['Doctrine\ORM\Mapping\JoinColumn', 'Doctrine\ORM\Mapping\Column'];

public function __construct(
Expand All @@ -45,16 +42,16 @@ public function resolve(Property $property, bool $forceNullable): ?Type
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);

$doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClasses(self::TO_ONE_ANNOTATION_CLASSES);
$doctrineAnnotationTagValueNode = $phpDocInfo->getByAnnotationClasses(CollectionMapping::TO_ONE_CLASSES);

if ($doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
return $this->resolveFromDocBlock($phpDocInfo, $property, $doctrineAnnotationTagValueNode, $forceNullable);
}

$expr = $this->attributeFinder->findAttributeByClassesArgByName(
$property,
self::TO_ONE_ANNOTATION_CLASSES,
'targetEntity'
CollectionMapping::TO_ONE_CLASSES,
EntityMappingKey::TARGET_ENTITY
);

if (! $expr instanceof Expr) {
Expand All @@ -78,7 +75,7 @@ private function processToOneRelation(
?DoctrineAnnotationTagValueNode $joinDoctrineAnnotationTagValueNode,
bool $forceNullable
): Type {
$targetEntityArrayItemNode = $toOneDoctrineAnnotationTagValueNode->getValue('targetEntity');
$targetEntityArrayItemNode = $toOneDoctrineAnnotationTagValueNode->getValue(EntityMappingKey::TARGET_ENTITY);
if (! $targetEntityArrayItemNode instanceof ArrayItemNode) {
return new MixedType();
}
Expand Down
15 changes: 6 additions & 9 deletions src/TypeAnalyzer/CollectionTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDoc\StringNode;
use Rector\Doctrine\CodeQuality\Enum\ToManyMappings;
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;
use Rector\Doctrine\CodeQuality\Enum\EntityMappingKey;
use Rector\Doctrine\CodeQuality\Enum\OdmMappingKey;
use Rector\Doctrine\NodeAnalyzer\AttrinationFinder;
use Rector\Doctrine\NodeAnalyzer\TargetEntityResolver;
use Rector\Doctrine\PhpDoc\ShortClassExpander;
Expand All @@ -27,11 +29,6 @@

final readonly class CollectionTypeResolver
{
/**
* @var string
*/
private const TARGET_ENTITY = 'targetEntity';

/**
* @var string
*/
Expand Down Expand Up @@ -69,7 +66,7 @@ public function resolveFromToManyProperty(Property $property): ?FullyQualifiedOb
{
$doctrineAnnotationTagValueNodeOrAttribute = $this->attrinationFinder->getByMany(
$property,
ToManyMappings::TO_MANY_CLASSES
CollectionMapping::TO_MANY_CLASSES
);

if ($doctrineAnnotationTagValueNodeOrAttribute instanceof DoctrineAnnotationTagValueNode) {
Expand All @@ -82,7 +79,7 @@ public function resolveFromToManyProperty(Property $property): ?FullyQualifiedOb
if ($doctrineAnnotationTagValueNodeOrAttribute instanceof Attribute) {
$targetEntityExpr = $this->findExprByArgNames(
$doctrineAnnotationTagValueNodeOrAttribute->args,
[self::TARGET_ENTITY, self::TARGET_DOCUMENT]
[EntityMappingKey::TARGET_ENTITY, OdmMappingKey::TARGET_DOCUMENT]
);

if (! $targetEntityExpr instanceof ClassConstFetch) {
Expand All @@ -104,7 +101,7 @@ private function resolveFromDoctrineAnnotationTagValueNode(
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode,
Property $property
): ?FullyQualifiedObjectType {
$targetEntityArrayItemNode = $doctrineAnnotationTagValueNode->getValue(self::TARGET_ENTITY);
$targetEntityArrayItemNode = $doctrineAnnotationTagValueNode->getValue(EntityMappingKey::TARGET_ENTITY);

// in case of ODM
$targetDocumentArrayItemNode = $doctrineAnnotationTagValueNode->getValue(self::TARGET_DOCUMENT);
Expand Down
4 changes: 2 additions & 2 deletions src/TypeAnalyzer/CollectionVarTagValueNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Doctrine\CodeQuality\Enum\ToManyMappings;
use Rector\Doctrine\CodeQuality\Enum\CollectionMapping;

final readonly class CollectionVarTagValueNodeResolver
{
Expand All @@ -19,7 +19,7 @@ public function __construct(
public function resolve(Property $property): ?VarTagValueNode
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
if (! $phpDocInfo->hasByAnnotationClasses(ToManyMappings::TO_MANY_CLASSES)) {
if (! $phpDocInfo->hasByAnnotationClasses(CollectionMapping::TO_MANY_CLASSES)) {
return null;
}

Expand Down

0 comments on commit 3ba7adb

Please sign in to comment.