[Doctrine] Add ChangeReturnTypeOfClassMethodWithGetIdRector (#2012)

[Doctrine] Add ChangeReturnTypeOfClassMethodWithGetIdRector
This commit is contained in:
Tomáš Votruba 2019-09-21 19:47:41 +02:00 committed by GitHub
commit 1fe20d3e0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 454 additions and 31 deletions

View File

@ -1,3 +1,5 @@
services:
Rector\Doctrine\Rector\MethodCall\ChangeSetIdToUuidValueRector: ~
Rector\Doctrine\Rector\MethodCall\ChangeGetUuidMethodCallToGetIdRector: ~
Rector\Doctrine\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector: ~
Rector\Doctrine\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector: ~

View File

@ -3,13 +3,18 @@
namespace Rector\DeadCode\Doctrine;
use Doctrine\ORM\Mapping\Entity;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\Type\ObjectType;
use Rector\Doctrine\PhpDocParser\DoctrineDocBlockResolver;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Class_\EntityTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Class_\InheritanceTypeTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\InversedByNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\MappedByNodeInterface;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\PhpParser\Node\Resolver\NameResolver;
@ -25,10 +30,26 @@ final class DoctrineEntityManipulator
*/
private $docBlockManipulator;
public function __construct(NameResolver $nameResolver, DocBlockManipulator $docBlockManipulator)
{
/**
* @var DoctrineDocBlockResolver
*/
private $doctrineDocBlockResolver;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(
NameResolver $nameResolver,
DocBlockManipulator $docBlockManipulator,
DoctrineDocBlockResolver $doctrineDocBlockResolver,
NodeTypeResolver $nodeTypeResolver
) {
$this->nameResolver = $nameResolver;
$this->docBlockManipulator = $docBlockManipulator;
$this->doctrineDocBlockResolver = $doctrineDocBlockResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function resolveOtherProperty(Property $property): ?string
@ -132,4 +153,22 @@ final class DoctrineEntityManipulator
return $manyToOnePropertyNames;
}
public function isMethodCallOnDoctrineEntity(Node $node, string $methodName): bool
{
if (! $node instanceof MethodCall) {
return false;
}
if (! $this->nameResolver->isName($node->name, $methodName)) {
return false;
}
$objectType = $this->nodeTypeResolver->getObjectType($node->var);
if (! $objectType instanceof ObjectType) {
return false;
}
return $this->doctrineDocBlockResolver->isDoctrineEntityClass($objectType->getClassName());
}
}

View File

@ -0,0 +1,102 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Rector\ClassMethod;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use Rector\DeadCode\Doctrine\DoctrineEntityManipulator;
use Rector\Doctrine\ValueObject\DoctrineClass;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\ChangeReturnTypeOfClassMethodWithGetIdRectorTest
*/
final class ChangeReturnTypeOfClassMethodWithGetIdRector extends AbstractRector
{
/**
* @var DoctrineEntityManipulator
*/
private $doctrineEntityManipulator;
public function __construct(DoctrineEntityManipulator $doctrineEntityManipulator)
{
$this->doctrineEntityManipulator = $doctrineEntityManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change getUuid() method call to getId()', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function getBuildingId(): int
{
$building = new Building();
return $building->getId();
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function getBuildingId(): \Ramsey\Uuid\UuidInterface
{
$building = new Building();
return $building->getId();
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($node->returnType === null) {
return null;
}
$hasEntityGetIdMethodCall = $this->hasEntityGetIdMethodCall($node);
if ($hasEntityGetIdMethodCall === false) {
return null;
}
$node->returnType = new FullyQualified(DoctrineClass::RAMSEY_UUID_INTERFACE);
return $node;
}
private function hasEntityGetIdMethodCall(Node $node): bool
{
return (bool) $this->betterNodeFinder->findFirst((array) $node->stmts, function (Node $node): bool {
if (! $node instanceof Return_) {
return false;
}
if ($node->expr === null) {
return false;
}
return $this->doctrineEntityManipulator->isMethodCallOnDoctrineEntity($node->expr, 'getId');
});
}
}

View File

@ -0,0 +1,119 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Rector\Identical;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PHPStan\Type\ObjectType;
use Rector\DeadCode\Doctrine\DoctrineEntityManipulator;
use Rector\Doctrine\ValueObject\DoctrineClass;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\ChangeIdenticalUuidToEqualsMethodCallRectorTest
*/
final class ChangeIdenticalUuidToEqualsMethodCallRector extends AbstractRector
{
/**
* @var DoctrineEntityManipulator
*/
private $doctrineEntityManipulator;
public function __construct(DoctrineEntityManipulator $doctrineEntityManipulator)
{
$this->doctrineEntityManipulator = $doctrineEntityManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change $uuid === 1 to $uuid->equals(\Ramsey\Uuid\Uuid::fromString(1))', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function match($checkedId): int
{
$building = new Building();
return $building->getId() === $checkedId;
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function match($checkedId): int
{
$building = new Building();
return $building->getId()->equals(\Ramsey\Uuid\Uuid::fromString($checkedId));
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Identical::class];
}
/**
* @param Identical $node
*/
public function refactor(Node $node): ?Node
{
$match = $this->matchEntityCallAndComparedVariable($node);
if ($match === null) {
return null;
}
[$entityMethodCall, $comparedVariable] = $match;
$fromStringValue = $this->createStaticCall(DoctrineClass::RAMSEY_UUID, 'fromString', [$comparedVariable]);
return $this->createMethodCall($entityMethodCall, 'equals', [$fromStringValue]);
}
private function isAlreadyUuidType(Expr $expr): bool
{
$comparedValueObjectType = $this->getStaticType($expr);
if (! $comparedValueObjectType instanceof ObjectType) {
return false;
}
return $comparedValueObjectType->getClassName() === DoctrineClass::RAMSEY_UUID_INTERFACE;
}
/**
* @return Expr[]|null
*/
private function matchEntityCallAndComparedVariable(Node $node): ?array
{
if ($this->doctrineEntityManipulator->isMethodCallOnDoctrineEntity($node->left, 'getId')) {
if ($this->isAlreadyUuidType($node->right)) {
return null;
}
return [$node->left, $node->right];
}
if ($this->doctrineEntityManipulator->isMethodCallOnDoctrineEntity($node->right, 'getId')) {
if ($this->isAlreadyUuidType($node->left)) {
return null;
}
return [$node->right, $node->left];
}
return null;
}
}

View File

@ -5,7 +5,7 @@ namespace Rector\Doctrine\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Type\ObjectType;
use Rector\DeadCode\Doctrine\DoctrineEntityManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
@ -15,6 +15,16 @@ use Rector\RectorDefinition\RectorDefinition;
*/
final class ChangeGetUuidMethodCallToGetIdRector extends AbstractRector
{
/**
* @var DoctrineEntityManipulator
*/
private $doctrineEntityManipulator;
public function __construct(DoctrineEntityManipulator $doctrineEntityManipulator)
{
$this->doctrineEntityManipulator = $doctrineEntityManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change getUuid() method call to getId()', [
@ -91,7 +101,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip($node)) {
if (! $this->doctrineEntityManipulator->isMethodCallOnDoctrineEntity($node, 'getUuid')) {
return null;
}
@ -99,18 +109,4 @@ PHP
return $node;
}
private function shouldSkip(Node\Expr\MethodCall $methodCall): bool
{
if (! $this->isName($methodCall->name, 'getUuid')) {
return true;
}
$methodVarObjectType = $this->getObjectType($methodCall->var);
if (! $methodVarObjectType instanceof ObjectType) {
return true;
}
return ! $this->isDoctrineEntityClass($methodVarObjectType->getClassName());
}
}

View File

@ -11,6 +11,7 @@ use PhpParser\Node\Stmt\Expression;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use Ramsey\Uuid\Uuid;
use Rector\DeadCode\Doctrine\DoctrineEntityManipulator;
use Rector\Doctrine\ValueObject\DoctrineClass;
use Rector\NodeContainer\ParsedNodesByType;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -30,9 +31,17 @@ final class ChangeSetIdToUuidValueRector extends AbstractRector
*/
private $parsedNodesByType;
public function __construct(ParsedNodesByType $parsedNodesByType)
{
/**
* @var DoctrineEntityManipulator
*/
private $doctrineEntityManipulator;
public function __construct(
ParsedNodesByType $parsedNodesByType,
DoctrineEntityManipulator $doctrineEntityManipulator
) {
$this->parsedNodesByType = $parsedNodesByType;
$this->doctrineEntityManipulator = $doctrineEntityManipulator;
}
public function getDefinition(): RectorDefinition
@ -142,16 +151,7 @@ PHP
private function shouldSkip(MethodCall $methodCall): bool
{
if (! $this->isName($methodCall->name, 'setId')) {
return true;
}
$objectType = $this->getObjectType($methodCall);
if (! $objectType instanceof ObjectType) {
return true;
}
if (! $this->isDoctrineEntityClass($objectType->getClassName())) {
if (! $this->doctrineEntityManipulator->isMethodCallOnDoctrineEntity($methodCall, 'setId')) {
return true;
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector;
use Rector\Doctrine\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class ChangeReturnTypeOfClassMethodWithGetIdRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
/**
* @return string[]
*/
public function provideDataForTest(): iterable
{
yield [__DIR__ . '/Fixture/fixture.php.inc'];
}
protected function getRectorClass(): string
{
return ChangeReturnTypeOfClassMethodWithGetIdRector::class;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\Fixture;
use Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\Source\Building;
class SomeClass
{
public function getBuildingId(): int
{
$building = new Building();
return $building->getId();
}
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\Fixture;
use Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\Source\Building;
class SomeClass
{
public function getBuildingId(): \Ramsey\Uuid\UuidInterface
{
$building = new Building();
return $building->getId();
}
}
?>

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\ClassMethod\ChangeReturnTypeOfClassMethodWithGetIdRector\Source;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Building
{
private $id;
public function getId(): \Ramsey\Uuid\UuidInterface
{
return $this->id;
}
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector;
use Rector\Doctrine\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class ChangeIdenticalUuidToEqualsMethodCallRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
/**
* @return string[]
*/
public function provideDataForTest(): iterable
{
yield [__DIR__ . '/Fixture/fixture.php.inc'];
}
protected function getRectorClass(): string
{
return ChangeIdenticalUuidToEqualsMethodCallRector::class;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\Fixture;
use Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\Source\Building;
class SomeClass
{
public function match($checkedId): int
{
$building = new Building();
$isLeft = $building->getId() === $checkedId;
$isRight = $checkedId === $building->getId();
}
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\Fixture;
use Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\Source\Building;
class SomeClass
{
public function match($checkedId): int
{
$building = new Building();
$isLeft = $building->getId()->equals(\Ramsey\Uuid\Uuid::fromString($checkedId));
$isRight = $building->getId()->equals(\Ramsey\Uuid\Uuid::fromString($checkedId));
}
}
?>

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Identical\ChangeIdenticalUuidToEqualsMethodCallRector\Source;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
final class Building
{
}

View File

@ -1,4 +1,6 @@
<?php declare(strict_types=1);
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';