decouple ClassPropertyCollector, [NamedServicesToContstructor] change mechanism to add new properties post traverse

This commit is contained in:
TomasVotruba 2017-07-20 18:37:43 +02:00
parent 01a5fd8b41
commit 80314eb924
12 changed files with 152 additions and 237 deletions

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace Rector\Buillder\Class_;
final class ClassPropertyCollector
{
/**
* @var string[][]
*/
private $classProperties = [];
public function addPropertyForClass(string $class, string $propertyType, string $propertyName): void
{
$this->classProperties[$class] = [
$propertyType => $propertyName
];
}
/**
* @return string[]
*/
public function getPropertiesforClass(string $class): array
{
return $this->classProperties[$class] ?: [];
}
}

View File

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace Rector\NodeVisitor\DependencyInjection\NamedServicesToConstructor;
use PhpParser\Node\Stmt\Class_;
use PhpParser\NodeVisitorAbstract;
use Rector\Builder\ConstructorMethodBuilder;
use Rector\Builder\PropertyBuilder;
use Rector\Buillder\Class_\ClassPropertyCollector;
/**
* Add new propertis to class and to contructor.
*/
final class AddPropertiesToClassNodeVisitor extends NodeVisitorAbstract
{
/**
* @var ConstructorMethodBuilder
*/
private $constructorMethodBuilder;
/**
* @var PropertyBuilder
*/
private $propertyBuilder;
/**
* @var string
*/
private $className;
/**
* @var ClassPropertyCollector
*/
private $newClassPropertyCollector;
public function __construct(
ConstructorMethodBuilder $constructorMethodBuilder,
PropertyBuilder $propertyBuilder,
ClassPropertyCollector $newClassPropertyCollector
) {
$this->constructorMethodBuilder = $constructorMethodBuilder;
$this->propertyBuilder = $propertyBuilder;
$this->newClassPropertyCollector = $newClassPropertyCollector;
}
public function afterTraverse(array $nodes): array
{
foreach ($nodes as $node) {
if ($node instanceof Class_) {
$this->className = (string) $node->name;
$this->reconstruct($node);
}
}
return $nodes;
}
private function reconstruct(Class_ $classNode): void
{
$propertiesForClass = $this->newClassPropertyCollector->getPropertiesforClass($this->className);
foreach ($propertiesForClass as $propertyType => $propertyName) {
$this->constructorMethodBuilder->addPropertyAssignToClass($classNode, $propertyType, $propertyName);
$this->propertyBuilder->addPropertyToClass($classNode, $propertyType, $propertyName);
}
}
}

View File

@ -11,17 +11,23 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\NodeVisitorAbstract; use PhpParser\NodeVisitorAbstract;
use Rector\Builder\Kernel\ServiceFromKernelResolver; use Rector\Builder\Kernel\ServiceFromKernelResolver;
use Rector\Builder\Naming\NameResolver; use Rector\Builder\Naming\NameResolver;
use Rector\Buillder\Class_\ClassPropertyCollector;
use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor\Source\LocalKernel; use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor\Source\LocalKernel;
/** /**
* Converts all: * Converts all:
* $this->get('some_service') * $this->get('some_service') # where "some_service" is name of the service in container
* *
* into: * into:
* $this->someService * $this->someService # where "someService" is type of the service
*/ */
final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract
{ {
/**
* @var string
*/
private $className;
/** /**
* @var NameResolver * @var NameResolver
*/ */
@ -32,10 +38,34 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract
*/ */
private $serviceFromKernelResolver; private $serviceFromKernelResolver;
public function __construct(NameResolver $nameResolver, ServiceFromKernelResolver $serviceFromKernelResolver) /**
{ * @var ClassPropertyCollector
*/
private $classPropertyCollector;
public function __construct(
NameResolver $nameResolver,
ServiceFromKernelResolver $serviceFromKernelResolver,
ClassPropertyCollector $classPropertyCollector
) {
$this->nameResolver = $nameResolver; $this->nameResolver = $nameResolver;
$this->serviceFromKernelResolver = $serviceFromKernelResolver; $this->serviceFromKernelResolver = $serviceFromKernelResolver;
$this->classPropertyCollector = $classPropertyCollector;
}
/**
* @param Node[] $nodes
* @return array|null
*/
public function beforeTraverse(array $nodes): ?array
{
foreach ($nodes as $node) {
if ($node instanceof Node\Stmt\Class_) {
$this->className = (string) $node->name;
}
}
return null;
} }
/** /**
@ -88,27 +118,17 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract
private function reconstruct(Node $assignOrMethodCallNode): void private function reconstruct(Node $assignOrMethodCallNode): void
{ {
if ($assignOrMethodCallNode instanceof Assign) { if ($assignOrMethodCallNode instanceof Assign) {
$this->processAssignment($assignOrMethodCallNode); $refactoredMethodCall = $this->processMethodCallNode($assignOrMethodCallNode->expr);
if ($refactoredMethodCall) {
$assignOrMethodCallNode->expr = $refactoredMethodCall;
}
} }
if ($assignOrMethodCallNode instanceof MethodCall) { if ($assignOrMethodCallNode instanceof MethodCall) {
$this->processMethodCall($assignOrMethodCallNode); $refactoredMethodCall = $this->processMethodCallNode($assignOrMethodCallNode->var);
} if ($refactoredMethodCall) {
} $assignOrMethodCallNode->var = $refactoredMethodCall;
}
private function processAssignment(Assign $assignNode): void
{
$refactoredMethodCall = $this->processMethodCallNode($assignNode->expr);
if ($refactoredMethodCall) {
$assignNode->expr = $refactoredMethodCall;
}
}
private function processMethodCall(MethodCall $methodCallNode): void
{
$refactoredMethodCall = $this->processMethodCallNode($methodCallNode->var);
if ($refactoredMethodCall) {
$methodCallNode->var = $refactoredMethodCall;
} }
} }
@ -142,8 +162,15 @@ final class GetterToPropertyNodeVisitor extends NodeVisitorAbstract
$serviceName, LocalKernel::class $serviceName, LocalKernel::class
); );
if ($serviceType === null) {
return null;
}
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType); $propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
$this->classPropertyCollector->addPropertyForClass($this->className, $serviceType, $propertyName);
return $this->createPropertyFetch($propertyName); return $this->createPropertyFetch($propertyName);
} }

View File

@ -1,203 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\NodeVisitor\DependencyInjection\NamedServicesToConstructor;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeVisitorAbstract;
use Rector\Builder\ConstructorMethodBuilder;
use Rector\Builder\Kernel\ServiceFromKernelResolver;
use Rector\Builder\Naming\NameResolver;
use Rector\Builder\PropertyBuilder;
use Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor\Source\LocalKernel;
/**
* Add property to class...
* Add property to constructor...
*
* How to dettect that particular class?
*/
final class NamedServicesToConstructorNodeVisitor extends NodeVisitorAbstract
{
/**
* @var ConstructorMethodBuilder
*/
private $constructorMethodBuilder;
/**
* @var PropertyBuilder
*/
private $propertyBuilder;
/**
* @var NameResolver
*/
private $nameResolver;
/**
* @var ServiceFromKernelResolver
*/
private $serviceFromKernelResolver;
public function __construct(
ConstructorMethodBuilder $constructorMethodBuilder,
PropertyBuilder $propertyBuilder,
NameResolver $nameResolver,
ServiceFromKernelResolver $serviceFromKernelResolver
) {
$this->constructorMethodBuilder = $constructorMethodBuilder;
$this->propertyBuilder = $propertyBuilder;
$this->nameResolver = $nameResolver;
$this->serviceFromKernelResolver = $serviceFromKernelResolver;
}
private function isCandidate(Node $node): bool
{
return $node instanceof Class_;
}
/**
* @param Class_ $classNode
*/
public function reconstruct(Node $classNode): void
{
foreach ($classNode->stmts as $insideClassNode) {
// 1. Detect method
if (! $insideClassNode instanceof ClassMethod) {
continue;
}
$methodNode = $insideClassNode;
foreach ($methodNode->stmts as $insideMethodNode) {
$insideMethodNode = $insideMethodNode->expr;
if ($insideMethodNode instanceof MethodCall && $insideMethodNode->var instanceof MethodCall) {
$this->processOnServiceMethodCall($classNode, $insideMethodNode);
// B. Find $var = $this->get('...');
} elseif ($insideMethodNode instanceof Assign) {
$this->processAssignment($classNode, $insideMethodNode);
}
}
}
}
private function processOnServiceMethodCall(Class_ $classNode, MethodCall $methodCallNode): void
{
if (! $this->isContainerGetCall($methodCallNode)) {
return;
}
$refactoredMethodCall = $this->processMethodCallNode($classNode, $methodCallNode->var);
if ($refactoredMethodCall) {
$methodCallNode->var = $refactoredMethodCall;
}
}
private function processAssignment(Class_ $classNode, Assign $assignNode): void
{
if (!$this->isContainerGetCall($assignNode)) {
return;
}
$this->processMethodCallNode($classNode, $assignNode->expr);
/*$refactoredMethodCall = */
// if ($refactoredMethodCall) {
// $assignNode->expr = $refactoredMethodCall;
// }
}
/**
* Accept only "$this->get('string')" statements.
*/
private function isContainerGetCall(Node $node): bool
{
if ($node instanceof Assign && ($node->expr instanceof MethodCall || $node->var instanceof MethodCall)) {
$methodCall = $node->expr;
} elseif ($node instanceof MethodCall && $node->var instanceof MethodCall) {
$methodCall = $node->var;
} else {
return false;
}
if ($methodCall->var->name !== 'this') {
return false;
}
if ((string) $methodCall->name !== 'get') {
return false;
}
if (! $methodCall->args[0]->value instanceof String_) {
return false;
}
return true;
}
private function processMethodCallNode(Class_ $classNode, MethodCall $methodCall): ?PropertyFetch
{
/** @var String_ $argument */
$argument = $methodCall->args[0]->value;
$serviceName = $argument->value;
$serviceType = $this->serviceFromKernelResolver->resolveServiceClassByNameFromKernel(
$serviceName, LocalKernel::class
);
if ($serviceType === null) {
return null;
}
// Get property name
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
// Add property assignment to constructor
$this->constructorMethodBuilder->addPropertyAssignToClass($classNode, $serviceType, $propertyName);
// 5. Add property to class
$this->propertyBuilder->addPropertyToClass($classNode, $serviceType, $propertyName);
// creates "$this->propertyName"
// return new PropertyFetch(
// new Variable('this', [
// 'name' => $propertyName
// ]), $propertyName
// );
return null;
}
/**
* Called when entering a node.
*
* Return value semantics:
* * null
* => $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|int|Node Replacement node (or special return value)
*/
public function enterNode(Node $node)
{
if ($this->isCandidate($node)) {
$this->reconstruct($node);
}
return null;
}
}

View File

@ -1,3 +1,7 @@
parameters:
# todo
kernel_class: # for name based service refactoring
services: services:
_defaults: _defaults:
autowire: true autowire: true

View File

@ -2,7 +2,6 @@
namespace Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor; namespace Rector\Tests\NodeVisitor\DependencyInjection\NamedServicesToConstructorReconstructor;
use Rector\NodeVisitor\DependencyInjection\NamedServicesToConstructor\NamedServicesToConstructorNodeVisitor;
use Rector\Testing\PHPUnit\AbstractReconstructorTestCase; use Rector\Testing\PHPUnit\AbstractReconstructorTestCase;
final class Test extends AbstractReconstructorTestCase final class Test extends AbstractReconstructorTestCase
@ -13,9 +12,4 @@ final class Test extends AbstractReconstructorTestCase
$this->doTestFileMatchesExpectedContent(__DIR__ . '/wrong/wrong2.php.inc', __DIR__ . '/correct/correct2.php.inc'); $this->doTestFileMatchesExpectedContent(__DIR__ . '/wrong/wrong2.php.inc', __DIR__ . '/correct/correct2.php.inc');
// $this->doTestFileMatchesExpectedContent(__DIR__ . '/wrong/wrong3.php.inc', __DIR__ . '/correct/correct3.php.inc'); // $this->doTestFileMatchesExpectedContent(__DIR__ . '/wrong/wrong3.php.inc', __DIR__ . '/correct/correct3.php.inc');
} }
protected function getNodeVisitorClass(): string
{
return NamedServicesToConstructorNodeVisitor::class;
}
} }

View File

@ -1,6 +1,6 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService extends Controller class ClassWithNamedService extends Controller
{ {
/** /**
* @var stdClass * @var stdClass

View File

@ -1,6 +1,6 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService implements ContainerAwareInterface class ClassWithNamedService implements ContainerAwareInterface
{ {
/** /**
* @var stdClass * @var stdClass

View File

@ -1,5 +1,5 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService implements ContainerAwareInterface class ClassWithNamedService implements ContainerAwareInterface
{ {
/** /**
* @var stdClass * @var stdClass

View File

@ -1,6 +1,6 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService extends Controller class ClassWithNamedService extends Controller
{ {
public function render() public function render()
{ {

View File

@ -1,6 +1,6 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService implements ContainerAwareInterface class ClassWithNamedService implements ContainerAwareInterface
{ {
public function render() public function render()
{ {

View File

@ -1,6 +1,6 @@
<?php declare (strict_types=1); <?php declare (strict_types=1);
class ClassWitNamedService implements ContainerAwareInterface class ClassWithNamedService implements ContainerAwareInterface
{ {
public function render() public function render()
{ {