mirror of
https://github.com/rectorphp/rector.git
synced 2025-01-18 05:48:21 +01:00
add priority to PropertyTypeInfererInterface and put doctrine infering first
This commit is contained in:
parent
6ebd32d327
commit
4e8ff948d8
@ -5,4 +5,4 @@ services:
|
||||
|
||||
Rector\TypeDeclaration\:
|
||||
resource: '../src/'
|
||||
exclude: '../src/{Rector/**/*Rector.php}'
|
||||
exclude: '../src/{Rector/**/*Rector.php,Exception}'
|
||||
|
@ -10,4 +10,9 @@ interface PropertyTypeInfererInterface
|
||||
* @return string[]
|
||||
*/
|
||||
public function inferProperty(Property $property): array;
|
||||
|
||||
/**
|
||||
* Higher priority goes first.
|
||||
*/
|
||||
public function getPriority(): int;
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Exception;
|
||||
|
||||
use Exception;
|
||||
use Rector\TypeDeclaration\Contract\PropertyTypeInfererInterface;
|
||||
|
||||
final class ConflictingPriorityException extends Exception
|
||||
{
|
||||
public function __construct(
|
||||
PropertyTypeInfererInterface $firstPropertyTypeInfererInterface,
|
||||
PropertyTypeInfererInterface $secondPropertyTypeInfererInterface
|
||||
) {
|
||||
$message = sprintf(
|
||||
'There are 2 property type inferers with %d priority:%s- %s%s- %s.%sChange value in "getPriority()" method in one of them to different value',
|
||||
$firstPropertyTypeInfererInterface->getPriority(),
|
||||
PHP_EOL,
|
||||
get_class($firstPropertyTypeInfererInterface),
|
||||
PHP_EOL,
|
||||
get_class($secondPropertyTypeInfererInterface),
|
||||
PHP_EOL
|
||||
);
|
||||
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PHPStan\Type\ArrayType;
|
||||
use PHPStan\Type\ErrorType;
|
||||
use PHPStan\Type\IntersectionType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
@ -36,6 +37,11 @@ final class AllAssignNodePropertyTypeInferer extends AbstractPropertyTypeInferer
|
||||
return $this->staticTypeToStringResolver->resolveObjectType($assignedExprStaticType);
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
@ -61,6 +67,10 @@ final class AllAssignNodePropertyTypeInferer extends AbstractPropertyTypeInferer
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($exprStaticType instanceof ErrorType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($node->var instanceof ArrayDimFetch) {
|
||||
$exprStaticType = new ArrayType(new MixedType(), $exprStaticType);
|
||||
}
|
||||
@ -70,7 +80,7 @@ final class AllAssignNodePropertyTypeInferer extends AbstractPropertyTypeInferer
|
||||
return null;
|
||||
});
|
||||
|
||||
return $assignedExprStaticTypes;
|
||||
return $this->filterOutDuplicatedTypes($assignedExprStaticTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,4 +108,23 @@ final class AllAssignNodePropertyTypeInferer extends AbstractPropertyTypeInferer
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Type[] $types
|
||||
* @return Type[]
|
||||
*/
|
||||
private function filterOutDuplicatedTypes(array $types): array
|
||||
{
|
||||
if (count($types) === 1) {
|
||||
return $types;
|
||||
}
|
||||
|
||||
$uniqueTypes = [];
|
||||
foreach ($types as $type) {
|
||||
$valueObjectHash = md5(serialize($type));
|
||||
$uniqueTypes[$valueObjectHash] = $type;
|
||||
}
|
||||
|
||||
return $uniqueTypes;
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,11 @@ final class ConstructorPropertyTypeInferer extends AbstractPropertyTypeInferer i
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 800;
|
||||
}
|
||||
|
||||
private function getResolveParamStaticTypeAsString(ClassMethod $classMethod, string $propertyName): ?string
|
||||
{
|
||||
$paramStaticType = $this->resolveParamStaticType($classMethod, $propertyName);
|
||||
|
@ -24,4 +24,9 @@ final class DefaultValuePropertyTypeInferer extends AbstractPropertyTypeInferer
|
||||
|
||||
return $this->staticTypeToStringResolver->resolveObjectType($nodeStaticType);
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 700;
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,11 @@ final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInte
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 1000;
|
||||
}
|
||||
|
||||
private function isNullable(string $value): bool
|
||||
{
|
||||
return (bool) Strings::match($value, '#nullable=true#');
|
||||
|
@ -59,6 +59,11 @@ final class DoctrineRelationPropertyTypeInferer implements PropertyTypeInfererIn
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 900;
|
||||
}
|
||||
|
||||
private function resolveTargetEntity(GenericTagValueNode $genericTagValueNode): ?string
|
||||
{
|
||||
$match = Strings::match($genericTagValueNode->value, '#targetEntity=\"(?<targetEntity>.*?)\"#');
|
||||
|
@ -56,4 +56,9 @@ final class GetterOrSetterPropertyTypeInferer extends AbstractPropertyTypeInfere
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,11 @@ final class SingleMethodAssignedNodePropertyTypeInferer implements PropertyTypeI
|
||||
return $stringTypes;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 750;
|
||||
}
|
||||
|
||||
private function resolveAssignedNodeToProperty(ClassMethod $classMethod, string $propertyName): ?Expr
|
||||
{
|
||||
$assignedNode = null;
|
||||
|
@ -10,6 +10,7 @@ use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\TypeDeclaration\Contract\PropertyTypeInfererInterface;
|
||||
use Rector\TypeDeclaration\Exception\ConflictingPriorityException;
|
||||
|
||||
final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
{
|
||||
@ -29,7 +30,8 @@ final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
public function __construct(DocBlockManipulator $docBlockManipulator, array $propertyTypeInferers = [])
|
||||
{
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->propertyTypeInferers = $propertyTypeInferers;
|
||||
|
||||
$this->sortAndSetPropertyTypeInferers($propertyTypeInferers);
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
@ -80,4 +82,27 @@ final class PropertyTypeDeclarationRector extends AbstractRector
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PropertyTypeInfererInterface[] $propertyTypeInferers
|
||||
*/
|
||||
private function sortAndSetPropertyTypeInferers(array $propertyTypeInferers): void
|
||||
{
|
||||
foreach ($propertyTypeInferers as $propertyTypeInferer) {
|
||||
$this->ensurePriorityIsUnique($propertyTypeInferer);
|
||||
$this->propertyTypeInferers[$propertyTypeInferer->getPriority()] = $propertyTypeInferer;
|
||||
}
|
||||
|
||||
krsort($this->propertyTypeInferers);
|
||||
}
|
||||
|
||||
private function ensurePriorityIsUnique(PropertyTypeInfererInterface $propertyTypeInferer): void
|
||||
{
|
||||
if (! isset($this->propertyTypeInferers[$propertyTypeInferer->getPriority()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$alreadySetPropertyTypeInferer = $this->propertyTypeInferers[$propertyTypeInferer->getPriority()];
|
||||
throw new ConflictingPriorityException($propertyTypeInferer, $alreadySetPropertyTypeInferer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\PropertyTypeDeclarationRector\Fixture;
|
||||
|
||||
use App\Api\Poll\Entity\Poll;
|
||||
use App\Api\User\Entity\User;
|
||||
use App\Api\User\Entity\UserList;
|
||||
use App\Entity\LocalizedString;
|
||||
use App\Entity\LocalizedStringList;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="answer")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Complex
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\LocalizedString", mappedBy="answerText", cascade={"all"})
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
* @ORM\OrderBy({"version" = "DESC"})
|
||||
*/
|
||||
private $answer;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="App\Api\User\Entity\User", inversedBy="answers", cascade={"persist", "merge"})
|
||||
*/
|
||||
private $voters;
|
||||
|
||||
public function getAnswer(): LocalizedStringList
|
||||
{
|
||||
if ($this->answer instanceof Collection) {
|
||||
return new LocalizedStringList($this->answer->getValues());
|
||||
}
|
||||
|
||||
if ($this->answer instanceof LocalizedStringList) {
|
||||
return $this->answer;
|
||||
}
|
||||
|
||||
return new LocalizedStringList($this->answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LocalizedStringList|LocalizedString[]|ArrayCollection $answer
|
||||
*/
|
||||
public function setAnswer($answer): void
|
||||
{
|
||||
if ($answer instanceof LocalizedStringList) {
|
||||
$this->answer = $answer->getLocalizedStrings();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($answer instanceof ArrayCollection) {
|
||||
$this->answer = $answer->getValues();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->answer = $answer;
|
||||
}
|
||||
|
||||
public function getVoters(): UserList
|
||||
{
|
||||
if ($this->voters instanceof Collection) {
|
||||
return new UserList($this->voters->getValues());
|
||||
}
|
||||
|
||||
if ($this->voters instanceof UserList) {
|
||||
return $this->voters;
|
||||
}
|
||||
|
||||
return new UserList($this->voters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserList|User[]|ArrayCollection $voters
|
||||
*/
|
||||
public function setVoters($voters): void
|
||||
{
|
||||
if ($voters instanceof UserList) {
|
||||
$this->voters = $voters->getUsers();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($voters instanceof ArrayCollection) {
|
||||
$this->voters = $voters->getValues();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->voters = $voters;
|
||||
}
|
||||
|
||||
public function addVoter(User $user): void
|
||||
{
|
||||
$this->voters[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\PropertyTypeDeclarationRector\Fixture;
|
||||
|
||||
use App\Api\Poll\Entity\Poll;
|
||||
use App\Api\User\Entity\User;
|
||||
use App\Api\User\Entity\UserList;
|
||||
use App\Entity\LocalizedString;
|
||||
use App\Entity\LocalizedStringList;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="answer")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Complex
|
||||
{
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\LocalizedString", mappedBy="answerText", cascade={"all"})
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
* @ORM\OrderBy({"version" = "DESC"})
|
||||
* @var \App\Entity\LocalizedString[]|\Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
private $answer;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="App\Api\User\Entity\User", inversedBy="answers", cascade={"persist", "merge"})
|
||||
* @var \App\Api\User\Entity\User[]|\Doctrine\Common\Collections\Collection
|
||||
*/
|
||||
private $voters;
|
||||
|
||||
public function getAnswer(): LocalizedStringList
|
||||
{
|
||||
if ($this->answer instanceof Collection) {
|
||||
return new LocalizedStringList($this->answer->getValues());
|
||||
}
|
||||
|
||||
if ($this->answer instanceof LocalizedStringList) {
|
||||
return $this->answer;
|
||||
}
|
||||
|
||||
return new LocalizedStringList($this->answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LocalizedStringList|LocalizedString[]|ArrayCollection $answer
|
||||
*/
|
||||
public function setAnswer($answer): void
|
||||
{
|
||||
if ($answer instanceof LocalizedStringList) {
|
||||
$this->answer = $answer->getLocalizedStrings();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($answer instanceof ArrayCollection) {
|
||||
$this->answer = $answer->getValues();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->answer = $answer;
|
||||
}
|
||||
|
||||
public function getVoters(): UserList
|
||||
{
|
||||
if ($this->voters instanceof Collection) {
|
||||
return new UserList($this->voters->getValues());
|
||||
}
|
||||
|
||||
if ($this->voters instanceof UserList) {
|
||||
return $this->voters;
|
||||
}
|
||||
|
||||
return new UserList($this->voters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserList|User[]|ArrayCollection $voters
|
||||
*/
|
||||
public function setVoters($voters): void
|
||||
{
|
||||
if ($voters instanceof UserList) {
|
||||
$this->voters = $voters->getUsers();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($voters instanceof ArrayCollection) {
|
||||
$this->voters = $voters->getValues();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->voters = $voters;
|
||||
}
|
||||
|
||||
public function addVoter(User $user): void
|
||||
{
|
||||
$this->voters[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -18,6 +18,8 @@ final class PropertyTypeDeclarationRectorTest extends AbstractRectorTestCase
|
||||
|
||||
__DIR__ . '/Fixture/doctrine_column.php.inc',
|
||||
__DIR__ . '/Fixture/doctrine_relation.php.inc',
|
||||
|
||||
__DIR__ . '/Fixture/complex.php.inc',
|
||||
// skip
|
||||
__DIR__ . '/Fixture/skip_multi_vars.php.inc',
|
||||
]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user