add priority to PropertyTypeInfererInterface and put doctrine infering first

This commit is contained in:
Tomas Votruba 2019-08-01 00:28:31 +02:00
parent 6ebd32d327
commit 4e8ff948d8
13 changed files with 331 additions and 3 deletions

View File

@ -5,4 +5,4 @@ services:
Rector\TypeDeclaration\:
resource: '../src/'
exclude: '../src/{Rector/**/*Rector.php}'
exclude: '../src/{Rector/**/*Rector.php,Exception}'

View File

@ -10,4 +10,9 @@ interface PropertyTypeInfererInterface
* @return string[]
*/
public function inferProperty(Property $property): array;
/**
* Higher priority goes first.
*/
public function getPriority(): int;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -24,4 +24,9 @@ final class DefaultValuePropertyTypeInferer extends AbstractPropertyTypeInferer
return $this->staticTypeToStringResolver->resolveObjectType($nodeStaticType);
}
public function getPriority(): int
{
return 700;
}
}

View File

@ -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#');

View File

@ -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>.*?)\"#');

View File

@ -56,4 +56,9 @@ final class GetterOrSetterPropertyTypeInferer extends AbstractPropertyTypeInfere
return [];
}
public function getPriority(): int
{
return 600;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}
?>

View File

@ -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',
]);