[Kdyby to Contributte] Add Subscriber migration

This commit is contained in:
TomasVotruba 2020-05-24 10:46:34 +02:00
parent beeb9b3458
commit 387ded5ecc
14 changed files with 720 additions and 3 deletions

View File

@ -133,7 +133,8 @@
"Rector\\Performance\\": "rules/performance/src",
"Rector\\Naming\\": "rules/naming/src",
"Rector\\Order\\": "rules/order/src",
"Rector\\MockistaToMockery\\": "rules/mockista-to-mockery/src"
"Rector\\MockistaToMockery\\": "rules/mockista-to-mockery/src",
"Rector\\NetteKdyby\\": "rules/nette-kdyby/src"
}
},
"autoload-dev": {
@ -204,7 +205,8 @@
"Rector\\Naming\\Tests\\": "rules/naming/tests",
"Rector\\Order\\Tests\\": "rules/order/tests",
"Rector\\MockistaToMockery\\Tests\\": "rules/mockista-to-mockery/tests",
"Rector\\Compiler\\Tests\\": "compiler/tests"
"Rector\\Compiler\\Tests\\": "compiler/tests",
"Rector\\NetteKdyby\\Tests\\": "rules/nette-kdyby/tests"
},
"classmap": [
"rules/cakephp/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Source",

View File

@ -0,0 +1,8 @@
# from: https://github.com/Kdyby/Events/
# to: https://github.com/contributte/event-dispatcher/
services:
Rector\Renaming\Rector\Class_\RenameClassRector:
$oldToNewClasses:
Kdyby\Events\Subscriber: 'Symfony\Component\EventDispatcher\EventSubscriberInterface'
Rector\NetteKdyby\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector: null

View File

@ -1,4 +1,4 @@
# All 487 Rectors Overview
# All 488 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -26,6 +26,7 @@
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (1)
- [Nette](#nette) (12)
- [NetteKdyby](#nettekdyby) (1)
- [NetteTesterToPHPUnit](#nettetestertophpunit) (3)
- [NetteToSymfony](#nettetosymfony) (9)
- [Order](#order) (3)
@ -4580,6 +4581,49 @@ Change $this->templates->{magic} to $this->template->render(..., $values)
<br>
## NetteKdyby
### `KdybyEventSubscriberToContributteEventSubscriberRector`
- class: [`Rector\NetteKdyby\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector`](/../master/rules/nette-kdyby/src/Rector/Class_/KdybyEventSubscriberToContributteEventSubscriberRector.php)
- [test fixtures](/../master/rules/nette-kdyby/tests/Rector/Class_/KdybyEventSubscriberToContributteEventSubscriberRector/Fixture)
Change EventSubscriber from Kdyby to Contributte
```diff
+use Contributte\Events\Extra\Event\Application\ShutdownEvent;
use Kdyby\Events\Subscriber;
use Nette\Application\Application;
-use Nette\Application\UI\Presenter;
class GetApplesSubscriber implements Subscriber
{
- public function getSubscribedEvents()
+ public static function getSubscribedEvents()
{
return [
- Application::class . '::onShutdown',
+ ShutdownEvent::class => 'onShutdown',
CustomService::class . '::onCopy' => 'onCustomCopy',
];
}
- public function onShutdown(Presenter $presenter)
+ public function onShutdown(ShutdownEvent $shutdownEvent)
{
+ $presenter = $shutdownEvent->getPresenter();
$presenterName = $presenter->getName();
// ...
}
public function onCustomCopy()
{
}
}
```
<br>
## NetteTesterToPHPUnit
### `NetteAssertToPHPUnitAssertRector`

View File

@ -25,6 +25,15 @@ final class ClassNaming
$this->nodeNameResolver = $nodeNameResolver;
}
/**
* @param string|Name|Identifier $name
*/
public function getVariableName($name): string
{
$shortName = $this->getShortName($name);
return lcfirst($shortName);
}
/**
* @param string|Name|Identifier $name
*/

View File

@ -0,0 +1,9 @@
services:
_defaults:
public: true
autowire: true
Rector\NetteKdyby\:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby;
use PhpParser\Node\Param;
use Rector\Core\Exception\NotImplementedException;
use Rector\NodeNameResolver\NodeNameResolver;
final class ContributeEventClassResolver
{
/**
* @var string[][]
*/
private const GETTER_METHODS_WITH_TYPE_BY_EVENT_CLASS = [
// application
'Contributte\Events\Extra\Event\Application\ShutdownEvent' => [
'Nette\Application\Application' => 'getApplication',
'Throwable' => 'getThrowable',
],
'Contributte\Events\Extra\Event\Application\StartupEvent' => [
'Nette\Application\Application' => 'getApplication',
],
'Contributte\Events\Extra\Event\Application\ErrorEvent' => [
'Nette\Application\Application' => 'getApplication',
'Throwable' => 'getThrowable',
],
'Contributte\Events\Extra\Event\Application\PresenterEvent' => [
'Nette\Application\Application' => 'getApplication',
'Nette\Application\IPresenter' => 'getPresenter',
],
'Contributte\Events\Extra\Event\Application\RequestEvent' => [
'Nette\Application\Application' => 'getApplication',
'Nette\Application\Request' => 'getRequest',
],
'Contributte\Events\Extra\Event\Application\ResponseEvent' => [
'Nette\Application\Application' => 'getApplication',
'Nette\Application\IResponse' => 'getResponse',
],
// presenter
'Contributte\Events\Extra\Event\Application\PresenterShutdownEvent' => [
'Nette\Application\IPresenter' => 'getPresenter',
'Nette\Application\IResponse' => 'getResponse',
],
'Contributte\Events\Extra\Event\Application\PresenterStartupEvent' => [
'Nette\Application\UI\Presenter' => 'getPresenter',
],
// nette/security
'Contributte\Events\Extra\Event\Security\LoggedInEvent' => [
'Nette\Security\User' => 'getUser',
],
'Contributte\Events\Extra\Event\Security\LoggedOutEvent' => [
'Nette\Security\User' => 'getUser',
],
// latte
'Contributte\Events\Extra\Event\Latte\LatteCompileEvent' => [
'Latte\Engine' => 'getEngine',
],
'Contributte\Events\Extra\Event\Latte\TemplateCreateEvent' => [
'Nette\Bridges\ApplicationLatte\Template' => 'getTemplate',
],
];
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}
public function resolveGetterMethodByEventClassAndParam(string $eventClass, Param $param): string
{
$getterMethodsWithType = self::GETTER_METHODS_WITH_TYPE_BY_EVENT_CLASS[$eventClass] ?? null;
if ($param->type === null) {
throw new NotImplementedException();
}
$type = $this->nodeNameResolver->getName($param->type);
if (isset($getterMethodsWithType[$type])) {
return $getterMethodsWithType[$type];
}
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\NodeManipulator;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name\FullyQualified;
use Rector\Core\PhpParser\Node\Value\ValueResolver;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NetteKdyby\ValueObject\NetteEventToContributeEventClass;
final class GetSubscribedEventsArrayManipulator
{
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var ValueResolver
*/
private $valueResolver;
public function __construct(CallableNodeTraverser $callableNodeTraverser, ValueResolver $valueResolver)
{
$this->callableNodeTraverser = $callableNodeTraverser;
$this->valueResolver = $valueResolver;
}
public function change(Array_ $array): void
{
$this->callableNodeTraverser->traverseNodesWithCallable($array->items, function (Node $node): ?Node {
if (! $node instanceof ArrayItem) {
return null;
}
foreach (NetteEventToContributeEventClass::PROPERTY_TO_EVENT_CLASS as $netteEventProperty => $contributeEventClass) {
if ($node->key === null) {
continue;
}
if (! $this->valueResolver->isValue($node->key, $netteEventProperty)) {
continue;
}
$node->key = new ClassConstFetch(new FullyQualified($contributeEventClass), 'class');
}
return $node;
});
}
}

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\NodeManipulator;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use Rector\CodingStyle\Naming\ClassNaming;
use Rector\NetteKdyby\ContributeEventClassResolver;
final class SubscriberMethodArgumentToContributteEventObjectManipulator
{
/**
* @var ClassNaming
*/
private $classNaming;
/**
* @var ContributeEventClassResolver
*/
private $contributeEventClassResolver;
public function __construct(
ClassNaming $classNaming,
ContributeEventClassResolver $contributeEventClassResolver
) {
$this->classNaming = $classNaming;
$this->contributeEventClassResolver = $contributeEventClassResolver;
}
/**
* @param array<string, ClassMethod> $classMethodsByEventClass
*/
public function change(array $classMethodsByEventClass): void
{
foreach ($classMethodsByEventClass as $eventClass => $classMethod) {
$oldParams = $classMethod->params;
$this->changeClassParamToEventClass($eventClass, $classMethod);
// move params
foreach ($oldParams as $oldParam) {
$eventGetterToVariableAssign = $this->createEventGetterToVariableMethodCall($eventClass, $oldParam);
$expression = new Expression($eventGetterToVariableAssign);
$classMethod->stmts = array_merge([$expression], (array) $classMethod->stmts);
}
}
}
private function changeClassParamToEventClass(string $eventClass, ClassMethod $classMethod): void
{
/** @var ClassMethod $classMethod */
$paramName = $this->classNaming->getVariableName($eventClass);
$eventVariable = new Variable($paramName);
$param = new Param($eventVariable, null, new FullyQualified($eventClass));
$classMethod->params = [$param];
}
private function createEventGetterToVariableMethodCall(string $eventClass, Param $param): Assign
{
$paramName = $this->classNaming->getVariableName($eventClass);
$eventVariable = new Variable($paramName);
$getterMethod = $this->contributeEventClassResolver->resolveGetterMethodByEventClassAndParam(
$eventClass,
$param
);
$methodCall = new MethodCall($eventVariable, $getterMethod);
return new Assign($param->var, $methodCall);
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\NodeResolver;
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\PhpParser\Node\Value\ValueResolver;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\NetteKdyby\ValueObject\NetteEventToContributeEventClass;
final class ListeningMethodsCollector
{
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var ValueResolver
*/
private $valueResolver;
public function __construct(CallableNodeTraverser $callableNodeTraverser, ValueResolver $valueResolver)
{
$this->callableNodeTraverser = $callableNodeTraverser;
$this->valueResolver = $valueResolver;
}
/**
* @return array<string, ClassMethod>
*/
public function collectFromClassAndGetSubscribedEventClassMethod(Class_ $class, ClassMethod $classMethod): array
{
$classMethodsByEventClass = [];
$this->callableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use (
$class,
&$classMethodsByEventClass
) {
if (! $node instanceof ArrayItem) {
return null;
}
$possibleMethodName = $this->valueResolver->getValue($node->value);
if (! is_string($possibleMethodName)) {
return null;
}
$classMethod = $class->getMethod($possibleMethodName);
if ($classMethod === null) {
return null;
}
if ($node->key === null) {
return null;
}
$eventClass = $this->valueResolver->getValue($node->key);
$contributeEventClasses = NetteEventToContributeEventClass::PROPERTY_TO_EVENT_CLASS;
if (! in_array($eventClass, $contributeEventClasses, true)) {
return null;
}
$classMethodsByEventClass[$eventClass] = $classMethod;
});
return $classMethodsByEventClass;
}
}

View File

@ -0,0 +1,211 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\Rector\Class_;
use Kdyby\Events\Subscriber;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\Exception\NotImplementedException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NetteKdyby\NodeManipulator\GetSubscribedEventsArrayManipulator;
use Rector\NetteKdyby\NodeManipulator\SubscriberMethodArgumentToContributteEventObjectManipulator;
use Rector\NetteKdyby\NodeResolver\ListeningMethodsCollector;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\NetteKdyby\Tests\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector\KdybyEventSubscriberToContributteEventSubscriberRectorTest
*/
final class KdybyEventSubscriberToContributteEventSubscriberRector extends AbstractRector
{
/**
* @var GetSubscribedEventsArrayManipulator
*/
private $getSubscribedEventsArrayManipulator;
/**
* @var SubscriberMethodArgumentToContributteEventObjectManipulator
*/
private $subscriberMethodArgumentToContributteEventObjectManipulator;
/**
* @var ListeningMethodsCollector
*/
private $listeningMethodsCollector;
public function __construct(
GetSubscribedEventsArrayManipulator $getSubscribedEventsArrayManipulator,
SubscriberMethodArgumentToContributteEventObjectManipulator $subscriberMethodArgumentToContributteEventObjectManipulator,
ListeningMethodsCollector $listeningMethodsCollector
) {
$this->getSubscribedEventsArrayManipulator = $getSubscribedEventsArrayManipulator;
$this->subscriberMethodArgumentToContributteEventObjectManipulator = $subscriberMethodArgumentToContributteEventObjectManipulator;
$this->listeningMethodsCollector = $listeningMethodsCollector;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change EventSubscriber from Kdyby to Contributte', [
new CodeSample(
<<<'PHP'
use Kdyby\Events\Subscriber;
use Nette\Application\Application;
use Nette\Application\UI\Presenter;
class GetApplesSubscriber implements Subscriber
{
public function getSubscribedEvents()
{
return [
Application::class . '::onShutdown',
CustomService::class . '::onCopy' => 'onCustomCopy',
];
}
public function onShutdown(Presenter $presenter)
{
$presenterName = $presenter->getName();
// ...
}
public function onCustomCopy()
{
}
}
PHP
,
<<<'PHP'
use Contributte\Events\Extra\Event\Application\ShutdownEvent;
use Kdyby\Events\Subscriber;
use Nette\Application\Application;
class GetApplesSubscriber implements Subscriber
{
public static function getSubscribedEvents()
{
return [
ShutdownEvent::class => 'onShutdown',
CustomService::class . '::onCopy' => 'onCustomCopy',
];
}
public function onShutdown(ShutdownEvent $shutdownEvent)
{
$presenter = $shutdownEvent->getPresenter();
$presenterName = $presenter->getName();
// ...
}
public function onCustomCopy()
{
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}
/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkipClassMethod($node)) {
return null;
}
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
if (! $class instanceof Class_) {
return null;
}
$this->makeStatic($node);
$this->refactorEventNames($node);
$listeningClassMethods = $this->listeningMethodsCollector->collectFromClassAndGetSubscribedEventClassMethod(
$class,
$node
);
$this->subscriberMethodArgumentToContributteEventObjectManipulator->change($listeningClassMethods);
return $node;
}
private function resolveMethodNameFromKdybyEventName(Expr $expr): string
{
$kdybyEventName = $this->getValue($expr);
if (Strings::contains($kdybyEventName, '::')) {
return (string) Strings::after($kdybyEventName, '::', - 1);
}
throw new NotImplementedException($kdybyEventName);
}
private function shouldSkipClassMethod($node): bool
{
$class = $node->getAttribute(AttributeKey::CLASS_NODE);
if ($class === null) {
return true;
}
if (! $this->isObjectType($class, Subscriber::class)) {
return true;
}
return ! $this->isName($node, 'getSubscribedEvents');
}
private function refactorArrayWithEventTable(Array_ $array): void
{
foreach ($array->items as $arrayItem) {
if ($arrayItem->key !== null) {
continue;
}
$methodName = $this->resolveMethodNameFromKdybyEventName($arrayItem->value);
$arrayItem->key = $arrayItem->value;
$arrayItem->value = new String_($methodName);
}
}
private function refactorEventNames(ClassMethod $classMethod): void
{
$this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) {
if (! $node instanceof Return_) {
return null;
}
if ($node->expr === null) {
return null;
}
$returnedExpr = $node->expr;
if (! $returnedExpr instanceof Array_) {
return null;
}
$this->refactorArrayWithEventTable($returnedExpr);
$this->getSubscribedEventsArrayManipulator->change($returnedExpr);
});
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\ValueObject;
final class NetteEventToContributeEventClass
{
/**
* @var string[]
* @see https://github.com/contributte/event-dispatcher-extra/tree/master/src/Event
*/
public const PROPERTY_TO_EVENT_CLASS = [
// application
'Nette\Application\Application::onShutdown' => 'Contributte\Events\Extra\Event\Application\ShutdownEvent',
'Nette\Application\Application::onStartup' => 'Contributte\Events\Extra\Event\Application\StartupEvent',
'Nette\Application\Application::onError' => 'Contributte\Events\Extra\Event\Application\ErrorEvent',
'Nette\Application\Application::onPresenter' => 'Contributte\Events\Extra\Event\Application\PresenterEvent',
'Nette\Application\Application::onRequest' => 'Contributte\Events\Extra\Event\Application\RequestEvent',
'Nette\Application\Application::onResponse' => 'Contributte\Events\Extra\Event\Application\ResponseEvent',
// presenter
'Nette\Application\UI\Presenter::onStartup' => 'Contributte\Events\Extra\Event\Application\PresenterShutdownEvent',
'Nette\Application\UI\Presenter::onShutdown' => 'Contributte\Events\Extra\Event\Application\PresenterStartupEvent',
// nette/security
'Nette\Security\User::onLoggedIn' => 'Contributte\Events\Extra\Event\Security\LoggedInEvent',
'Nette\Security\User::onLoggedOut' => 'Contributte\Events\Extra\Event\Security\LoggedOutEvent',
// latte
'Latte\Engine::onCompile' => 'Contributte\Events\Extra\Event\Latte\LatteCompileEvent',
'Nette\Bridges\ApplicationLatte\TemplateFactory::onCreate' => 'Contributte\Events\Extra\Event\Latte\TemplateCreateEvent',
];
}

View File

@ -0,0 +1,58 @@
<?php
namespace Rector\NetteKdyby\Tests\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector\Fixture;
use Kdyby\Events\Subscriber;
use Nette\Application\Application;
class GetApplesSubscriber implements Subscriber
{
public function getSubscribedEvents()
{
return [
Application::class . '::onShutdown',
CustomService::class . '::onCopy' => 'onCustomCopy',
];
}
public function onShutdown(Application $application): void
{
$presenter = $application->getPresenter();
}
public function onCustomCopy()
{
}
}
?>
-----
<?php
namespace Rector\NetteKdyby\Tests\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector\Fixture;
use Kdyby\Events\Subscriber;
use Nette\Application\Application;
class GetApplesSubscriber implements Subscriber
{
public static function getSubscribedEvents()
{
return [
\Contributte\Events\Extra\Event\Application\ShutdownEvent::class => 'onShutdown',
CustomService::class . '::onCopy' => 'onCustomCopy',
];
}
public function onShutdown(\Contributte\Events\Extra\Event\Application\ShutdownEvent $shutdownEvent): void
{
$application = $shutdownEvent->getApplication();
$presenter = $application->getPresenter();
}
public function onCustomCopy()
{
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\NetteKdyby\Tests\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\NetteKdyby\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector;
final class KdybyEventSubscriberToContributteEventSubscriberRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return KdybyEventSubscriberToContributteEventSubscriberRector::class;
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kdyby\Events;
if (interface_exists('Kdyby\Events\Subscriber')) {
return;
}
interface Subscriber
{
}