Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

League event: change dispatch string events to objects #6029

Merged
merged 9 commits into from
Apr 9, 2021
5 changes: 4 additions & 1 deletion config/set/league-event-30.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Rector\Composer\Rector\ChangePackageVersionComposerRector;
use Rector\Composer\ValueObject\PackageAndVersion;
use Rector\Core\Configuration\Option;
use Rector\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector;
use Rector\Removing\Rector\Class_\RemoveInterfacesRector;
use Rector\Removing\Rector\Class_\RemoveParentRector;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
Expand Down Expand Up @@ -80,7 +81,7 @@
$services->set(AddInterfaceByParentRector::class)
->call('configure', [[
AddInterfaceByParentRector::INTERFACE_BY_PARENT => [
'League\Event\AbstractEvent' => 'League\Event\HasEventName',
'League\Event\Event' => 'League\Event\HasEventName',
'League\Event\AbstractListener' => 'League\Event\Listener',
],
]]);
Expand All @@ -98,4 +99,6 @@
'League\Event\AbstractListener',
],
]]);

$services->set(DispatchStringToObjectRector::class);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class DispatchStringToObjectRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Rector\Tests\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector\Fixture;

class Fixture
{
/** @var \League\Event\EventDispatcher */
private $dispatcher;

public function run()
{
$this->dispatcher->dispatch('my-event');
}
}

?>
-----
<?php

namespace Rector\Tests\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector\Fixture;

class Fixture
{
/** @var \League\Event\EventDispatcher */
private $dispatcher;

public function run()
{
$this->dispatcher->dispatch(new class implements \League\Event\HasEventName
{
public function eventName(): string
{
return 'my-event';
}
});
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

use Rector\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();

$services->set(DispatchStringToObjectRector::class);
};
137 changes: 137 additions & 0 deletions rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

namespace Rector\LeagueEvent\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector\DispatchStringToObjectRectorTest
*/
final class DispatchStringToObjectRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change string events to anonymous class which implement \League\Event\HasEventName', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
/** @var \League\Event\EventDispatcher */
private $dispatcher;

public function run()
{
$this->dispatcher->dispatch('my-event');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
/** @var \League\Event\EventDispatcher */
private $dispatcher;

public function run()
{
$this->dispatcher->dispatch(new class implements \League\Event\HasEventName
lulco marked this conversation as resolved.
Show resolved Hide resolved
{
public function eventName(): string
{
return 'my-event';
}
});
}
}
CODE_SAMPLE
)
]);
}

public function getNodeTypes(): array
{
return [MethodCall::class];
}

/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkip($node)) {
return null;
}

return $this->updateNode($node);
}

private function shouldSkip(MethodCall $methodCall): bool
{
if (! $this->isNames($methodCall->name, ['dispatch', 'emit'])) {
return true;
}

if ($this->isObjectType($methodCall->var, new ObjectType('League\Event\EventDispatcher'))) {
return false;
}

if ($this->isObjectType($methodCall->var, new ObjectType('League\Event\Emitter'))) {
return false;
}

if (! $this->getStaticType($methodCall->args[0]->value) instanceof StringType) {
return true;
}

return true;
}

private function updateNode(MethodCall $methodCall): MethodCall
{
$methodCall->args[0] = new Arg($this->createNewAnonymousEventClass($methodCall->args[0]->value));
return $methodCall;
}

private function createNewAnonymousEventClass(Expr $eventName): New_
{
$implements = [
new FullyQualified('League\Event\HasEventName')
];

return new New_(new Class_(null, [
'implements' => $implements,
'stmts' => $this->createAnonymousEventClassBody($eventName),
]));
}

/**
* @param Expr $eventName
* @return Stmt[]
*/
private function createAnonymousEventClassBody(Expr $eventName): array
{
return [
new ClassMethod('eventName', [
'flags' => Class_::MODIFIER_PUBLIC,
'returnType' => 'string',
'stmts' => [
new Return_($eventName),
],
]),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function refactor(Node $node): ?Node
{
/** @var Scope $scope */
$scope = $node->getAttribute(AttributeKey::SCOPE);

$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return null;
Expand Down