Skip to content

Commit

Permalink
Keep selectable intersection when improving doctrine collection doc type
Browse files Browse the repository at this point in the history
  • Loading branch information
bobvandevijver committed Aug 30, 2024
1 parent ef84d74 commit 64af294
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture\OneToMany\Attribute;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Source\CountryRef;

#[ORM\Entity]
class SkipCollectionGenericClass
{
/**
* @var Collection<int, CountryRef>&Selectable<int, CountryRef>
*/
#[ORM\OneToMany(mappedBy: 'country', targetEntity: CountryRef::class, cascade: ['persist'])]
private Collection&Selectable $countryRefs;

public function __construct()
{
$this->countryRefs = new ArrayCollection();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Rector\Doctrine\Tests\CodeQuality\Rector\Property\ImproveDoctrineCollectionDocTypeInEntityRector\Fixture\OneToMany\Attribute;

use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class SkipCollectionGenericObject
{
/**
* @var Collection<int, object>&Selectable<int, object>
*/
#[ORM\OneToMany(mappedBy: 'port', targetEntity: object::class)]
private Collection&Selectable $oi;

public function __construct()
{
$this->oi = new ArrayCollection();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
Expand All @@ -33,6 +34,8 @@
*/
final class ImproveDoctrineCollectionDocTypeInEntityRector extends AbstractRector
{
private readonly ObjectType $selectableObjectType;

public function __construct(
private readonly CollectionTypeFactory $collectionTypeFactory,
private readonly CollectionTypeResolver $collectionTypeResolver,
Expand All @@ -44,6 +47,7 @@ public function __construct(
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly SetterCollectionResolver $setterCollectionResolver,
) {
$this->selectableObjectType = new ObjectType('Doctrine\Common\Collections\Selectable');
}

public function getRuleDefinition(): RuleDefinition
Expand Down Expand Up @@ -207,19 +211,16 @@ private function refactorPropertyPhpDocInfo(Property $property, PhpDocInfo $phpD
if (! $collectionObjectType instanceof FullyQualifiedObjectType) {
return null;
}

$newVarType = $this->collectionTypeFactory->createType($collectionObjectType);
$this->phpDocTypeChanger->changeVarType($property, $phpDocInfo, $newVarType);
} else {
$collectionObjectType = $this->collectionTypeResolver->resolveFromToManyProperty($property);
if (! $collectionObjectType instanceof FullyQualifiedObjectType) {
return null;
}

$newVarType = $this->collectionTypeFactory->createType($collectionObjectType);
$this->phpDocTypeChanger->changeVarType($property, $phpDocInfo, $newVarType);
}

$newVarType = $this->collectionTypeFactory->createType($collectionObjectType);
$this->phpDocTypeChanger->changeVarType($property, $phpDocInfo, $newVarType);

return $property;
}

Expand Down Expand Up @@ -249,7 +250,17 @@ private function refactorAttribute(Expr $expr, PhpDocInfo $phpDocInfo, Property

$fullyQualifiedObjectType = new FullyQualifiedObjectType($targetEntityClassName);

$genericObjectType = $this->collectionTypeFactory->createType($fullyQualifiedObjectType);
$addSelectableUnion = false;
if ($property->type instanceof Node\IntersectionType) {
foreach ($property->type->types as $type) {
if ($this->selectableObjectType->isSuperTypeOf(new ObjectType($type->toCodeString()))->yes()) {
$addSelectableUnion = true;
break;
}
}
}

$genericObjectType = $this->collectionTypeFactory->createType($fullyQualifiedObjectType, $addSelectableUnion);
$this->phpDocTypeChanger->changeVarType($property, $phpDocInfo, $genericObjectType);

return $property;
Expand Down
12 changes: 10 additions & 2 deletions src/TypeAnalyzer/CollectionTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@

use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

final class CollectionTypeFactory
{
public function createType(ObjectType $objectType): GenericObjectType
public function createType(ObjectType $objectType, bool $addSelectableUnion = false): Type
{
$genericTypes = [new IntegerType(), $objectType];
return new GenericObjectType('Doctrine\Common\Collections\Collection', $genericTypes);
$collectionType = new GenericObjectType('Doctrine\Common\Collections\Collection', $genericTypes);
if (!$addSelectableUnion) {
return $collectionType;
}

$selectableType = new GenericObjectType('Doctrine\Common\Collections\Selectable', $genericTypes);
return new IntersectionType([$collectionType, $selectableType]);
}
}
2 changes: 1 addition & 1 deletion stubs/Common/Collections/ArrayCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
return;
}

class ArrayCollection implements Collection
class ArrayCollection implements Collection, Selectable
{

public function add(mixed $element)
Expand Down
14 changes: 14 additions & 0 deletions stubs/Common/Collections/Selectable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Doctrine\Common\Collections;

if (interface_exists('Doctrine\Common\Collections\Selectable')) {
return;
}

interface Selectable
{

}

0 comments on commit 64af294

Please sign in to comment.