[Doctrine] Add registry to EM

This commit is contained in:
Tomas Votruba 2019-07-02 13:05:52 +02:00
parent c3f9fb9e44
commit bd48d5792d
9 changed files with 528 additions and 0 deletions

View File

@ -0,0 +1,2 @@
services:
Rector\Doctrine\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector: ~

View File

@ -0,0 +1,8 @@
services:
_defaults:
autowire: true
public: true
Rector\Doctrine\:
resource: '../src'
exclude: '../src/{Rector/**/*Rector.php}'

View File

@ -0,0 +1,330 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PhpParser\NodeTraverser;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
final class ManagerRegistryGetManagerToEntityManagerRector extends AbstractRector
{
/**
* @var string
*/
private const MANAGER_REGISTRY_CLASS = 'Doctrine\Common\Persistence\ManagerRegistry';
/**
* @var string
*/
private const ENTITY_MANAGER_CLASS = 'Doctrine\ORM\EntityManagerInterface';
/**
* @var string
*/
private const OBJECT_MANAGER_CLASS = 'Doctrine\Common\Persistence\ObjectManager';
/**
* @var string
*/
private $managerRegistryClass;
/**
* @var string
*/
private $objectManagerClass;
public function __construct(
string $managerRegistryClass = self::MANAGER_REGISTRY_CLASS,
string $objectManagerClass = self::OBJECT_MANAGER_CLASS
) {
$this->managerRegistryClass = $managerRegistryClass;
$this->objectManagerClass = $objectManagerClass;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('', [
new CodeSample(
<<<'CODE_SAMPLE'
use Doctrine\Common\Persistence\ManagerRegistry;
class CustomRepository
{
/**
* @var ManagerRegistry
*/
private $managerRegistry;
public function __construct(ManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function run()
{
$entityManager = $this->managerRegistry->getManager();
$someRepository = $entityManager->getRepository('Some');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Doctrine\ORM\EntityManagerInterface;
class CustomRepository
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function run()
{
$someRepository = $this->entityManager->getRepository('Some');
}
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$constructMethodNode = $node->getMethod('__construct');
if ($constructMethodNode === null) {
return null;
}
// collect on registry method calls, so we know if the manager registry is needed
$registryCalledMethods = $this->resolveManagerRegistryCalledMethodNames($node);
$shouldRemoveManagerRegistryProperty = $registryCalledMethods === ['getManager'];
if (! in_array('getManager', $registryCalledMethods, true)) {
return null;
}
$registryParam = $this->resolveManagerRegistryParam($constructMethodNode);
// no registry manager in the constructor
if ($registryParam === null) {
return null;
}
// add entity manager via constructor
$this->addConstructorDependencyWithProperty(
$node,
$constructMethodNode,
'entityManager',
self::ENTITY_MANAGER_CLASS
);
if ($shouldRemoveManagerRegistryProperty === true) {
$this->removeManagerRegistryDependency($node, $constructMethodNode, $registryParam);
}
// remove assign method calls
$this->removeAssignGetRepositoryCalls($node);
$this->traverseNodesWithCallable($node->stmts, function (Node $node): ?PropertyFetch {
if (! $node instanceof Variable) {
return null;
}
if (! $this->isType($node, $this->objectManagerClass)) {
return null;
}
return new PropertyFetch(new Variable('this'), 'entityManager');
});
return $node;
}
private function isRegistryGetManagerMethodCall(Assign $assign): bool
{
if (! $assign->expr instanceof MethodCall) {
return false;
}
if (! $this->isType($assign->expr->var, $this->managerRegistryClass)) {
return false;
}
if (! $this->isName($assign->expr->name, 'getManager')) {
return false;
}
return true;
}
private function removeAssignGetRepositoryCalls(Class_ $class): void
{
$this->traverseNodesWithCallable($class->stmts, function (Node $node) {
if (! $node instanceof Assign) {
return null;
}
if (! $this->isRegistryGetManagerMethodCall($node)) {
return null;
}
$this->removeNode($node);
});
}
private function createEntityManagerParam(): Param
{
return new Param(new Variable('entityManager'), null, new FullyQualified(self::ENTITY_MANAGER_CLASS));
}
/**
* @return string[]
*/
private function resolveManagerRegistryCalledMethodNames(Class_ $class): array
{
$registryCalledMethods = [];
$this->traverseNodesWithCallable($class->stmts, function (Node $node) use (&$registryCalledMethods) {
if (! $node instanceof MethodCall) {
return null;
}
if (! $this->isType($node->var, $this->managerRegistryClass)) {
return null;
}
$name = $this->getName($node);
if ($name === null) {
return null;
}
$registryCalledMethods[] = $name;
});
return array_unique($registryCalledMethods);
}
private function removeManagerRegistryDependency(
Class_ $class,
ClassMethod $classMethod,
Param $registryParam
): void {
// remove constructor param: $managerRegistry
foreach ($classMethod->params as $key => $param) {
if ($param->type === null) {
continue;
}
if (! $this->isName($param->type, $this->managerRegistryClass)) {
continue;
}
unset($classMethod->params[$key]);
}
$this->removeRegistryDependencyAssign($class, $classMethod, $registryParam);
}
private function addConstructorDependencyWithProperty(
Class_ $class,
ClassMethod $classMethod,
string $name,
string $type
): void {
$propertyFetch = new PropertyFetch(new Variable('this'), $name);
$assign = new Assign($propertyFetch, new Variable($name));
$classMethod->stmts[] = new Expression($assign);
$this->addPropertyToClass($class, $type, $name);
}
private function resolveManagerRegistryParam(ClassMethod $classMethod): ?Param
{
foreach ($classMethod->params as $param) {
if ($param->type === null) {
continue;
}
if (! $this->isName($param->type, $this->managerRegistryClass)) {
continue;
}
$classMethod->params[] = $this->createEntityManagerParam();
return $param;
}
return null;
}
private function removeManagerRegistryProperty(Class_ $class, Assign $assign): void
{
$managerRegistryPropertyName = $this->getName($assign->var);
$this->traverseNodesWithCallable($class->stmts, function (Node $node) use (
$managerRegistryPropertyName
): ?int {
if (! $node instanceof Property) {
return null;
}
if (! $this->isName($node, $managerRegistryPropertyName)) {
return null;
}
$this->removeNode($node);
return NodeTraverser::STOP_TRAVERSAL;
});
}
private function removeRegistryDependencyAssign(Class_ $class, ClassMethod $classMethod, Param $registryParam): void
{
foreach ((array) $classMethod->stmts as $key => $constructorMethodStmt) {
if (! $constructorMethodStmt instanceof Expression && ! $constructorMethodStmt->expr instanceof Assign) {
continue;
}
/** @var Assign $assign */
$assign = $constructorMethodStmt->expr;
if (! $this->areNamesEqual($assign->expr, $registryParam->var)) {
continue;
}
$this->removeManagerRegistryProperty($class, $assign);
// remove assign
unset($classMethod->stmts[$key]);
break;
}
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Fixture;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
class DoNotRemoveRegistryOnNonGetRepoCall
{
/**
* @var DummyManagerRegistry
*/
private $managerRegistry;
public function __construct(DummyManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function run()
{
$manager = $this->managerRegistry->getManager();
$item = $manager->getItem();
$this->managerRegistry->kickThat();
}
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Fixture;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
class DoNotRemoveRegistryOnNonGetRepoCall
{
/**
* @var DummyManagerRegistry
*/
private $managerRegistry;
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $entityManager;
public function __construct(DummyManagerRegistry $managerRegistry, \Doctrine\ORM\EntityManagerInterface $entityManager)
{
$this->managerRegistry = $managerRegistry;
$this->entityManager = $entityManager;
}
public function run()
{
$item = $this->entityManager->getItem();
$this->managerRegistry->kickThat();
}
}
?>

View File

@ -0,0 +1,52 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Fixture;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
class CustomRepository
{
/**
* @var DummyManagerRegistry
*/
private $managerRegistry;
public function __construct(DummyManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function run()
{
$entityManager = $this->managerRegistry->getManager();
$someRepository = $entityManager->getRepository('Some');
}
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Fixture;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
class CustomRepository
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $entityManager;
public function __construct(\Doctrine\ORM\EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function run()
{
$someRepository = $this->entityManager->getRepository('Some');
}
}
?>

View File

@ -0,0 +1,24 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Fixture;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
class KeepDifferntMethods
{
/**
* @var DummyManagerRegistry
*/
private $managerRegistry;
public function __construct(DummyManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function run()
{
$this->managerRegistry->resetThis();
$this->managerRegistry->kickThat();
}
}

View File

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector;
use Rector\Doctrine\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyManagerRegistry;
use Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source\DummyObjectManager;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class ManagerRegistryGetManagerToEntityManagerRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/keep_different_methods.php.inc',
__DIR__ . '/Fixture/do_not_remove_registry_on_non_get_repo_call.php.inc',
]);
}
/**
* @return mixed[]
*/
protected function getRectorsWithConfiguration(): array
{
return [
ManagerRegistryGetManagerToEntityManagerRector::class => [
'$managerRegistryClass' => DummyManagerRegistry::class,
'$objectManagerClass' => DummyObjectManager::class,
],
];
}
}

View File

@ -0,0 +1,11 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source;
final class DummyManagerRegistry
{
public function getManager(): DummyObjectManager
{
return new DummyObjectManager();
}
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\ManagerRegistryGetManagerToEntityManagerRector\Source;
final class DummyObjectManager
{
}