Skip to content

Commit

Permalink
[Reconstructor] NamedServicesToConstructor init
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jul 16, 2017
1 parent ad5ba53 commit e59acf0
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"symfony/console": "^3.3",
"symfony/dependency-injection": "^3.3",
"nikic/php-parser": "^3.0",
"ocramius/code-generator-utils": "^0.4.1"
"ocramius/code-generator-utils": "^0.4",
"nette/utils": "^2.4"
},
"require-dev": {
"phpunit/phpunit": "^6.2",
Expand Down
22 changes: 20 additions & 2 deletions src/Builder/ConstructorMethodBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Rector\Builder;

use Nette\Utils\Arrays;
use PhpParser\Builder\Method;
use PhpParser\Builder\Param;
use PhpParser\BuilderFactory;
Expand Down Expand Up @@ -49,7 +50,7 @@ public function addPropertyAssignToClass(Class_ $classNode, string $propertyType
->addParam($this->createParameter($propertyType, $propertyName))
->addStmts($assign);

$classNode->stmts[] = $constructorMethod->getNode();
$this->addAsFirstMethod($classNode, $constructorMethod);
}

private function createParameter(string $propertyType, string $propertyName): Param
Expand All @@ -69,4 +70,21 @@ private function createPropertyAssignment(string $propertyName): array
$propertyName
));
}
}

private function addAsFirstMethod(Class_ $classNode, Method $constructorMethod): void
{
foreach ($classNode->stmts as $key => $classElementNode) {
if ($classElementNode instanceof ClassMethod) {
Arrays::insertBefore(
$classNode->stmts,
$key,
[$constructorMethod->getNode()]
);

return;
}
}

$classNode->stmts[] = $constructorMethod->getNode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php declare(strict_types=1);

namespace Rector\Reconstructor\DependencyInjection;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Builder\ConstructorMethodBuilder;
use Rector\Contract\Dispatcher\ReconstructorInterface;
use Rector\Tests\Reconstructor\DependencyInjection\NamedServicesToConstructorReconstructor\Source\LocalKernel;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Kernel;

final class NamedServicesToConstructorReconstructor implements ReconstructorInterface
{
/**
* @var ConstructorMethodBuilder
*/
private $constructorMethodBuilder;

public function __construct(ConstructorMethodBuilder $constructorMethodBuilder)
{
$this->constructorMethodBuilder = $constructorMethodBuilder;
}

public function isCandidate(Node $node): bool
{
return $node instanceof Class_;
}

/**
* @param Class_|Node $classNode
*/
public function reconstruct(Node $classNode): void
{
foreach ($classNode->stmts as $classElementStatement) {
// 1. Detect method
if (! $classElementStatement instanceof ClassMethod) {
continue;
}

$classMethodNode = $classElementStatement;

foreach ($classMethodNode->stmts as $classMethodStatement) {
// 2. Find ->get('...') call in it
if (! $classMethodStatement instanceof MethodCall) {
continue;
}

$methodCallNode = $classMethodStatement;
// A. Find ->get('...')->someCall()
/**
* @todo: process also $var = $this->get('...');
* not a MethodCall on service, but Assign/PropertyFetch
*/
if (! $methodCallNode->var instanceof MethodCall) {
continue;
}

$methodCallNode = $methodCallNode->var;

// 3. Accept only "$this->get()"
if ($methodCallNode->name !== 'get') {
continue;
}

// 4. Accept only strings in "$this->get('string')"
$argument = $methodCallNode->args[0]->value;
if (! $methodCallNode->args[0]->value instanceof String_) {
continue;
}

/** @var String_ $argument */
$serviceName = $argument->value;

$container = $this->getContainerFromKernelClass();
if (! $container->has($serviceName)) {
// service name could not be found
continue;
}

$service = $container->get($serviceName);

// 6. Save Services
$serviceType = get_class($service);
$propertyName = $this->createPropertyNameFromClass($serviceType);
$collectedServices[$propertyName] = $serviceType;

// 7. Replace "$this->get()" => "$this->{$propertyName}"
// A.

// 7.1 Replace "$this" with "$this->propertyName"
$methodCallNode->var = new PropertyFetch(
new Variable('this', [
'name' => $propertyName
]), '' // @todo: with annotation!
);

// 8. add this property to constructor
$this->constructorMethodBuilder->addPropertyAssignToClass($classNode, $serviceType, $propertyName);
}
}
}

/**
* @todo extract to helper service, LocalKernelProvider::get...()
*/
private function getContainerFromKernelClass(): ContainerInterface
{
/** @var Kernel $kernel */
$kernel = new LocalKernel('dev', true);
$kernel->boot();

// @todo: initialize without creating cache or log directory
// @todo: call only loadBundles() and initializeContainer() methods

return $kernel->getContainer();
}

private function createPropertyNameFromClass(string $serviceType): string
{
$serviceNameParts = explode('\\', $serviceType);
$lastNamePart = array_pop($serviceNameParts);

return lcfirst($lastNamePart);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace Rector\Tests\Reconstructor\DependencyInjection\NamedServicesToConstructorReconstructor\Source;

use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\Kernel;

final class LocalKernel extends Kernel
{
/**
* @return BundleInterface[]
*/
public function registerBundles(): array
{
return [];
}

public function registerContainerConfiguration(LoaderInterface $loader): void
{
$loader->load(__DIR__ . '/services.yml');
}

public function getCacheDir(): string
{
return sys_get_temp_dir() . '/_rector_tests_local_kernel_cache';
}

public function getLogDir(): string
{
return sys_get_temp_dir() . '/_rector_tests_local_kernel_logs';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
services:
some.class:
class: stdClass
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);

namespace Rector\Tests\Reconstructor\DependencyInjection\NamedServicesToConstructorReconstructor;

use Rector\Reconstructor\DependencyInjection\NamedServicesToConstructorReconstructor;
use Rector\Testing\PHPUnit\AbstractReconstructorTestCase;

final class Test extends AbstractReconstructorTestCase
{
public function test(): void
{
$this->doTestFileMatchesExpectedContent(
__DIR__ . '/wrong/wrong.php.inc',
__DIR__ . '/correct/correct.php.inc'
);
}

protected function getReconstructorClass(): string
{
return NamedServicesToConstructorReconstructor::class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare (strict_types=1);
class ClassWitNamedService
{
/**
* @var SomeClass
*/
private $someClass;
public function __construct(SomeClass $someClass)
{
$this->someClass = $someClass;
}
public function render()
{
$this->someClass->render();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare (strict_types=1);

class ClassWitNamedService
{
public function render()
{
$this->get('some.class')->render();
}
}

0 comments on commit e59acf0

Please sign in to comment.