Skip to content
3 changes: 3 additions & 0 deletions config/rector/sets/cakephp60.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
declare(strict_types=1);

use Cake\Upgrade\Rector\Rector\MethodCall\EventManagerOnRector;
use PHPStan\Type\ObjectType;
use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
Expand All @@ -16,6 +17,8 @@

# @see https://book.cakephp.org/6/en/appendices/6-0-migration-guide.html
return static function (RectorConfig $rectorConfig): void {
// EventManager::on() signature change
$rectorConfig->rule(EventManagerOnRector::class);

// Changes related to the accessible => patchable rename
$rectorConfig->ruleWithConfiguration(RenameMethodRector::class, [
Expand Down
75 changes: 75 additions & 0 deletions src/Rector/Rector/MethodCall/EventManagerOnRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);

namespace Cake\Upgrade\Rector\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Type\ObjectType;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* Swaps the 2nd and 3rd arguments of EventManagerInterface::on() when called with 3 arguments.
*
* @see \Cake\Upgrade\Test\TestCase\Rector\MethodCall\EventManagerOnRector\EventManagerOnRectorTest
*/
final class EventManagerOnRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Swaps the 2nd and 3rd arguments of EventManagerInterface::on() to match new signature',
[
new CodeSample(
<<<'CODE_SAMPLE'
$eventManager->on('Model.beforeSave', ['priority' => 90], $callable);
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$eventManager->on('Model.beforeSave', $callable, ['priority' => 90]);
CODE_SAMPLE,
),
],
);
}

/**
* @return array<class-string<\PhpParser\Node>>
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}

/**
* @param \PhpParser\Node\Expr\MethodCall $node
*/
public function refactor(Node $node): ?Node
{
// Check if this is a call to the 'on' method
if (!$this->isName($node->name, 'on')) {
return null;
}

// Check if the object implements EventManagerInterface
if (!$this->isObjectType($node->var, new ObjectType('Cake\Event\EventManagerInterface'))) {
return null;
}

// Only process if there are exactly 3 arguments
if (count($node->args) !== 3) {
return null;
}

// Swap the 2nd and 3rd arguments
$secondArg = $node->args[1];
$thirdArg = $node->args[2];

$node->args[1] = $thirdArg;
$node->args[2] = $secondArg;

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

namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\EventManagerOnRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class EventManagerOnRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::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,67 @@
<?php

namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\EventManagerOnRector\Fixture;

use Cake\Event\EventManager;

class Fixture
{
public function run()
{
$eventManager = EventManager::instance();

// Should be transformed - 3 arguments with array literal
$eventManager->on('Model.beforeSave', ['priority' => 90], $callable);

// Should be transformed - 3 arguments with different options
$eventManager->on('Controller.initialize', ['priority' => 10], function () {
return true;
});

// Should NOT be transformed - 2 arguments
$eventManager->on('Model.afterSave', $callable);

// Should NOT be transformed - 1 argument
$eventManager->on('Model.afterDelete');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a valid usecase but I can clean that up later


// Should be transformed - 3 arguments with variable
$options = ['priority' => 100];
$eventManager->on('Model.beforeFind', $options, $handler);
}
}

?>
-----
<?php

namespace Cake\Upgrade\Test\TestCase\Rector\MethodCall\EventManagerOnRector\Fixture;

use Cake\Event\EventManager;

class Fixture
{
public function run()
{
$eventManager = EventManager::instance();

// Should be transformed - 3 arguments with array literal
$eventManager->on('Model.beforeSave', $callable, ['priority' => 90]);

// Should be transformed - 3 arguments with different options
$eventManager->on('Controller.initialize', function () {
return true;
}, ['priority' => 10]);

// Should NOT be transformed - 2 arguments
$eventManager->on('Model.afterSave', $callable);

// Should NOT be transformed - 1 argument
$eventManager->on('Model.afterDelete');

// Should be transformed - 3 arguments with variable
$options = ['priority' => 100];
$eventManager->on('Model.beforeFind', $handler, $options);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);

use Cake\Upgrade\Rector\Rector\MethodCall\EventManagerOnRector;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(EventManagerOnRector::class);
};
Loading