From c5aa3aa18cd9ea86acf57857976e908d9cc07556 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Fri, 2 Apr 2021 22:52:18 +0200 Subject: [PATCH 1/6] League event: change dispatch string events to objects --- config/set/league-event-30.php | 5 +- .../DispatchStringToObjectRectorTest.php | 32 +++++++ .../Fixture/fixture.php.inc | 53 +++++++++++ .../config/configured_rule.php | 12 +++ .../Rector/DispatchStringToObjectRector.php | 94 +++++++++++++++++++ .../Class_/AddInterfaceByParentRector.php | 6 ++ 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php create mode 100644 rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/Fixture/fixture.php.inc create mode 100644 rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php create mode 100644 rules/LeagueEvent/Rector/DispatchStringToObjectRector.php diff --git a/config/set/league-event-30.php b/config/set/league-event-30.php index be323e8417f5..1e488e351326 100644 --- a/config/set/league-event-30.php +++ b/config/set/league-event-30.php @@ -8,6 +8,7 @@ use Rector\Composer\Rector\ChangePackageVersionComposerRector; use Rector\Composer\ValueObject\PackageAndVersion; use Rector\Core\Configuration\Option; +use Rector\LeagueEvent\Rector\DispatchStringToObjectRector; use Rector\Removing\Rector\Class_\RemoveInterfacesRector; use Rector\Removing\Rector\Class_\RemoveParentRector; use Rector\Renaming\Rector\MethodCall\RenameMethodRector; @@ -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', ], ]]); @@ -98,4 +99,6 @@ 'League\Event\AbstractListener', ], ]]); + + $services->set(DispatchStringToObjectRector::class); }; diff --git a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php new file mode 100644 index 000000000000..1f70afa75d8a --- /dev/null +++ b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php @@ -0,0 +1,32 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} + diff --git a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/Fixture/fixture.php.inc b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..7e795408c75c --- /dev/null +++ b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/Fixture/fixture.php.inc @@ -0,0 +1,53 @@ +dispatcher->dispatch('my-event'); + $this->emitter->dispatch('your-event'); + } +} + +?> +----- +dispatcher->dispatch(new class implements \League\Event\HasEventName + { + public function eventName(): string + { + return 'my-event'; + } + }); + $this->emitter->dispatch(new class implements \League\Event\HasEventName + { + public function eventName(): string + { + return 'your-event'; + } + }); + } +} + +?> diff --git a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php new file mode 100644 index 000000000000..33af18edcf93 --- /dev/null +++ b/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php @@ -0,0 +1,12 @@ +services(); + + $services->set(DispatchStringToObjectRector::class); +}; diff --git a/rules/LeagueEvent/Rector/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/DispatchStringToObjectRector.php new file mode 100644 index 000000000000..9505a132011a --- /dev/null +++ b/rules/LeagueEvent/Rector/DispatchStringToObjectRector.php @@ -0,0 +1,94 @@ +dispatcher->dispatch('my-event'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class SomeClass +{ + public function run() + { + $this->dispatcher->dispatch(new class implements \League\Event\HasEventName + { + 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 + { + $methodName = $this->getName($node->name); + if ($methodName !== 'dispatch') { + return null; + } + + if (! ($this->isObjectType($node->var, new ObjectType('League\Event\EventDispatcher')) || $this->isObjectType($node->var, new ObjectType('League\Event\Emitter')))) { // at this point $node->var should be League\Event\EventDispatcher because class rename was executed, but it is not for some reason (maybe some cache?) + return null; + } + + if (! $this->getStaticType($node->args[0]->value) instanceof StringType) { + return null; + } + + $node->args[0] = new Arg(new New_(new Class_(null, [ + 'implements' => [ + new FullyQualified('League\Event\HasEventName') + ], + 'stmts' => [ + new ClassMethod('eventName', [ + 'flags' => Class_::MODIFIER_PUBLIC, + 'returnType' => 'string', + 'stmts' => [ + new Return_($node->args[0]->value), + ], + ]), + ] + ]))); + + return $node; + } +} diff --git a/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php b/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php index 2070c310e7f0..212a6c92e11f 100644 --- a/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php +++ b/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php @@ -70,6 +70,12 @@ public function refactor(Node $node): ?Node { /** @var Scope $scope */ $scope = $node->getAttribute(AttributeKey::SCOPE); + + // anonymous class has no scope + if ($scope === null) { + return null; + } + $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { return null; From a0532104cabfce2f73b80eee4aed263874fd55e1 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Fri, 2 Apr 2021 23:57:40 +0200 Subject: [PATCH 2/6] Fixed PHPStan --- config/set/league-event-30.php | 2 +- phpstan.neon | 5 +++++ .../DispatchStringToObjectRectorTest.php | 2 +- .../Fixture/fixture.php.inc | 18 ++---------------- .../config/configured_rule.php | 2 +- .../DispatchStringToObjectRector.php | 13 +++++++++++-- .../Class_/AddInterfaceByParentRector.php | 5 ----- 7 files changed, 21 insertions(+), 26 deletions(-) rename rules-tests/LeagueEvent/Rector/{ => MethodCall}/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php (88%) rename rules-tests/LeagueEvent/Rector/{ => MethodCall}/DispatchStringToObjectRector/Fixture/fixture.php.inc (50%) rename rules-tests/LeagueEvent/Rector/{ => MethodCall}/DispatchStringToObjectRector/config/configured_rule.php (81%) rename rules/LeagueEvent/Rector/{ => MethodCall}/DispatchStringToObjectRector.php (84%) diff --git a/config/set/league-event-30.php b/config/set/league-event-30.php index 1e488e351326..63f60fad6d97 100644 --- a/config/set/league-event-30.php +++ b/config/set/league-event-30.php @@ -8,7 +8,7 @@ use Rector\Composer\Rector\ChangePackageVersionComposerRector; use Rector\Composer\ValueObject\PackageAndVersion; use Rector\Core\Configuration\Option; -use Rector\LeagueEvent\Rector\DispatchStringToObjectRector; +use Rector\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector; use Rector\Removing\Rector\Class_\RemoveInterfacesRector; use Rector\Removing\Rector\Class_\RemoveParentRector; use Rector\Renaming\Rector\MethodCall\RenameMethodRector; diff --git a/phpstan.neon b/phpstan.neon index 71f122b7b2a3..515a3fc7ca3b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -339,6 +339,11 @@ parameters: paths: - config/set/* + - + message: '#new is limited to 3 "new \(new \)\)" nesting to each other. You have \d+ nesting.#' + paths: + - rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php + - '#Class with base "FileNode" name is already used in "PHPStan\\Node\\FileNode", "Rector\\Core\\PhpParser\\Node\\CustomNode\\FileNode"\. Use unique name to make classes easy to recognize#' # buggy phpstan clas-string diff --git a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php b/rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php similarity index 88% rename from rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php rename to rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php index 1f70afa75d8a..1a86d5d7e170 100644 --- a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php +++ b/rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/DispatchStringToObjectRectorTest.php @@ -1,6 +1,6 @@ dispatcher->dispatch('my-event'); - $this->emitter->dispatch('your-event'); } } @@ -21,16 +17,13 @@ class Fixture ----- dispatcher->dispatch(new class implements \League\Event\HasEventName @@ -40,13 +33,6 @@ class Fixture return 'my-event'; } }); - $this->emitter->dispatch(new class implements \League\Event\HasEventName - { - public function eventName(): string - { - return 'your-event'; - } - }); } } diff --git a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php b/rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/config/configured_rule.php similarity index 81% rename from rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php rename to rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/config/configured_rule.php index 33af18edcf93..9d11957b55ce 100644 --- a/rules-tests/LeagueEvent/Rector/DispatchStringToObjectRector/config/configured_rule.php +++ b/rules-tests/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector/config/configured_rule.php @@ -2,7 +2,7 @@ declare(strict_types=1); -use Rector\LeagueEvent\Rector\DispatchStringToObjectRector; +use Rector\LeagueEvent\Rector\MethodCall\DispatchStringToObjectRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { diff --git a/rules/LeagueEvent/Rector/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php similarity index 84% rename from rules/LeagueEvent/Rector/DispatchStringToObjectRector.php rename to rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php index 9505a132011a..050293358919 100644 --- a/rules/LeagueEvent/Rector/DispatchStringToObjectRector.php +++ b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php @@ -1,6 +1,6 @@ dispatcher->dispatch('my-event'); @@ -35,6 +41,9 @@ public function run() <<<'CODE_SAMPLE' final class SomeClass { + /** @var \League\Event\EventDispatcher */ + private $dispatcher; + public function run() { $this->dispatcher->dispatch(new class implements \League\Event\HasEventName @@ -66,7 +75,7 @@ public function refactor(Node $node): ?Node return null; } - if (! ($this->isObjectType($node->var, new ObjectType('League\Event\EventDispatcher')) || $this->isObjectType($node->var, new ObjectType('League\Event\Emitter')))) { // at this point $node->var should be League\Event\EventDispatcher because class rename was executed, but it is not for some reason (maybe some cache?) + if (! $this->isObjectType($node->var, new ObjectType('League\Event\EventDispatcher'))) { return null; } diff --git a/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php b/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php index 212a6c92e11f..6d0beb73dbee 100644 --- a/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php +++ b/rules/Transform/Rector/Class_/AddInterfaceByParentRector.php @@ -71,11 +71,6 @@ public function refactor(Node $node): ?Node /** @var Scope $scope */ $scope = $node->getAttribute(AttributeKey::SCOPE); - // anonymous class has no scope - if ($scope === null) { - return null; - } - $classReflection = $scope->getClassReflection(); if (! $classReflection instanceof ClassReflection) { return null; From 589a506bbef26de8ea06c2e9e3bfeeb1e341b4d1 Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Sat, 3 Apr 2021 23:12:55 +0200 Subject: [PATCH 3/6] Extracted implements and statements --- phpstan.neon | 5 ---- .../DispatchStringToObjectRector.php | 26 ++++++++++--------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 515a3fc7ca3b..71f122b7b2a3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -339,11 +339,6 @@ parameters: paths: - config/set/* - - - message: '#new is limited to 3 "new \(new \)\)" nesting to each other. You have \d+ nesting.#' - paths: - - rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php - - '#Class with base "FileNode" name is already used in "PHPStan\\Node\\FileNode", "Rector\\Core\\PhpParser\\Node\\CustomNode\\FileNode"\. Use unique name to make classes easy to recognize#' # buggy phpstan clas-string diff --git a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php index 050293358919..6ec1a7c66b3f 100644 --- a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php +++ b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php @@ -83,19 +83,21 @@ public function refactor(Node $node): ?Node return null; } + $implements = [ + new FullyQualified('League\Event\HasEventName') + ]; + $statements = [ + new ClassMethod('eventName', [ + 'flags' => Class_::MODIFIER_PUBLIC, + 'returnType' => 'string', + 'stmts' => [ + new Return_($node->args[0]->value), + ], + ]), + ]; $node->args[0] = new Arg(new New_(new Class_(null, [ - 'implements' => [ - new FullyQualified('League\Event\HasEventName') - ], - 'stmts' => [ - new ClassMethod('eventName', [ - 'flags' => Class_::MODIFIER_PUBLIC, - 'returnType' => 'string', - 'stmts' => [ - new Return_($node->args[0]->value), - ], - ]), - ] + 'implements' => $implements, + 'stmts' => $statements, ]))); return $node; From 1c368ce187a4326f6e55af3b3ace89956ed4a91f Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Mon, 5 Apr 2021 22:38:53 +0200 Subject: [PATCH 4/6] Refactored code and added support for League\Event\Emitter::emit --- .../DispatchStringToObjectRector.php | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php index 6ec1a7c66b3f..a5411df92aeb 100644 --- a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php +++ b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php @@ -4,6 +4,7 @@ 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; @@ -70,36 +71,62 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $methodName = $this->getName($node->name); - if ($methodName !== 'dispatch') { + if ($this->shouldSkip($node)) { return null; } - if (! $this->isObjectType($node->var, new ObjectType('League\Event\EventDispatcher'))) { - return null; + return $this->updateNode($node); + } + + private function shouldSkip(MethodCall $methodCall): bool + { + if (! $this->isNames($methodCall->name, ['dispatch', 'emit'])) { + return true; } - if (! $this->getStaticType($node->args[0]->value) instanceof StringType) { - return null; + 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') ]; - $statements = [ + + return new New_(new Class_(null, [ + 'implements' => $implements, + 'stmts' => $this->createAnonymousEventClassBody($eventName), + ])); + } + + private function createAnonymousEventClassBody(Expr $eventName): array + { + return [ new ClassMethod('eventName', [ 'flags' => Class_::MODIFIER_PUBLIC, 'returnType' => 'string', 'stmts' => [ - new Return_($node->args[0]->value), + new Return_($eventName), ], ]), ]; - $node->args[0] = new Arg(new New_(new Class_(null, [ - 'implements' => $implements, - 'stmts' => $statements, - ]))); - - return $node; } } From f72aaf73113beec123482579bf24d5420975746d Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Mon, 5 Apr 2021 22:49:47 +0200 Subject: [PATCH 5/6] Added specific typehint for array return type --- .../Rector/MethodCall/DispatchStringToObjectRector.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php index a5411df92aeb..48363bfde781 100644 --- a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php +++ b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php @@ -8,6 +8,7 @@ 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_; @@ -117,6 +118,10 @@ private function createNewAnonymousEventClass(Expr $eventName): New_ ])); } + /** + * @param Expr $eventName + * @return Stmt[] + */ private function createAnonymousEventClassBody(Expr $eventName): array { return [ From 63ae6072d1460d6f8be857e54edb63fa90c0d78f Mon Sep 17 00:00:00 2001 From: Michal Lulco Date: Mon, 5 Apr 2021 22:51:59 +0200 Subject: [PATCH 6/6] Changed description --- .../Rector/MethodCall/DispatchStringToObjectRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php index 48363bfde781..4ff6dd7dc080 100644 --- a/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php +++ b/rules/LeagueEvent/Rector/MethodCall/DispatchStringToObjectRector.php @@ -25,7 +25,7 @@ final class DispatchStringToObjectRector extends AbstractRector { public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Change string events to objects which implement \League\Event\HasEventName', [ + return new RuleDefinition('Change string events to anonymous class which implement \League\Event\HasEventName', [ new CodeSample( <<<'CODE_SAMPLE' final class SomeClass