From 0947f74a6a741d17e233f99bc3b89b470bc1581e Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Wed, 22 Jul 2026 20:26:50 +0200 Subject: [PATCH] Add cakephp54 rector ruleset The 5.4 migration guide already tells users to run bin/cake upgrade rector --rules cakephp54 but no such ruleset existed, so the command failed. Covers the two mechanically automatable 5.4 changes: - Console helpers moved from the Cake\Command\Helper namespace to Cake\Console\Helper. The core keeps class_alias shims, so this is a plain class rename. - SelectQuery::disableHydration() is deprecated in favor of the new Table::unhydratedFind(), which returns an UnhydratedSelectQuery whose static type matches the array result shape. The new DisableHydrationToUnhydratedFindRector collapses a find()->...->disableHydration() chain into unhydratedFind()->..., and skips receivers that are not a Table as well as detached queries where the originating find() call is not part of the chain. --- README.md | 4 + config/rector/cakephp54.php | 10 +++ config/rector/sets/cakephp54.php | 17 +++++ ...DisableHydrationToUnhydratedFindRector.php | 75 +++++++++++++++++++ src/Rector/Set/CakePHPSetList.php | 5 ++ tests/TestCase/Command/RectorCommandTest.php | 7 ++ ...bleHydrationToUnhydratedFindRectorTest.php | 28 +++++++ .../Fixture/fixture.php.inc | 39 ++++++++++ .../Fixture/skip_detached_query.php.inc | 21 ++++++ .../Fixture/skip_not_a_table.php.inc | 27 +++++++ .../config/configured_rule.php | 9 +++ .../src/SomeTest.php | 20 +++++ .../src/SomeTest.php | 20 +++++ 13 files changed, 282 insertions(+) create mode 100644 config/rector/cakephp54.php create mode 100644 config/rector/sets/cakephp54.php create mode 100644 src/Rector/Rector/MethodCall/DisableHydrationToUnhydratedFindRector.php create mode 100644 tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/DisableHydrationToUnhydratedFindRectorTest.php create mode 100644 tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/fixture.php.inc create mode 100644 tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_detached_query.php.inc create mode 100644 tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_not_a_table.php.inc create mode 100644 tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/config/configured_rule.php create mode 100644 tests/test_apps/original/RectorCommand-testApply54/src/SomeTest.php create mode 100644 tests/test_apps/upgraded/RectorCommand-testApply54/src/SomeTest.php diff --git a/README.md b/README.md index accc6aa7..6f0a21e9 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ cd /path/to/upgrade # To apply upgrade rules from 5.2 to 5.3 bin/cake upgrade rector --rules cakephp53 /path/to/your/app/src + +# To apply upgrade rules from 5.3 to 5.4 +bin/cake upgrade rector --rules cakephp54 /path/to/your/app/src ``` There are rules included for: @@ -98,6 +101,7 @@ There are rules included for: - cakephp51 - cakephp52 - cakephp53 +- cakephp54 ## Additional Rulesets diff --git a/config/rector/cakephp54.php b/config/rector/cakephp54.php new file mode 100644 index 00000000..bec27a5d --- /dev/null +++ b/config/rector/cakephp54.php @@ -0,0 +1,10 @@ +import(__DIR__ . '/defaults.php'); + $rectorConfig->sets([CakePHPSetList::CAKEPHP_54]); +}; diff --git a/config/rector/sets/cakephp54.php b/config/rector/sets/cakephp54.php new file mode 100644 index 00000000..6277e365 --- /dev/null +++ b/config/rector/sets/cakephp54.php @@ -0,0 +1,17 @@ +ruleWithConfiguration(RenameClassRector::class, [ + 'Cake\Command\Helper\BannerHelper' => 'Cake\Console\Helper\BannerHelper', + 'Cake\Command\Helper\ProgressHelper' => 'Cake\Console\Helper\ProgressHelper', + 'Cake\Command\Helper\TableHelper' => 'Cake\Console\Helper\TableHelper', + 'Cake\Command\Helper\TreeHelper' => 'Cake\Console\Helper\TreeHelper', + ]); + $rectorConfig->rule(DisableHydrationToUnhydratedFindRector::class); +}; diff --git a/src/Rector/Rector/MethodCall/DisableHydrationToUnhydratedFindRector.php b/src/Rector/Rector/MethodCall/DisableHydrationToUnhydratedFindRector.php new file mode 100644 index 00000000..6c23d161 --- /dev/null +++ b/src/Rector/Rector/MethodCall/DisableHydrationToUnhydratedFindRector.php @@ -0,0 +1,75 @@ +disableHydration() to Table::unhydratedFind(). + * + * @see https://book.cakephp.org/5/en/appendices/5-4-migration-guide.html + */ +final class DisableHydrationToUnhydratedFindRector extends AbstractRector +{ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change Table::find()->disableHydration() to Table::unhydratedFind()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +$articles->find('all')->disableHydration(); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +$articles->unhydratedFind('all'); +CODE_SAMPLE, + ), + ], + ); + } + + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + public function refactor(Node $node): ?Node + { + if (!$node instanceof MethodCall) { + return null; + } + + if (!$node->name instanceof Identifier || $node->name->toString() !== 'disableHydration') { + return null; + } + + if (count($node->args) !== 0) { + return null; + } + + $current = $node->var; + while ($current instanceof MethodCall) { + if ($current->name instanceof Identifier && $current->name->toString() === 'find') { + if (!(new ObjectType('Cake\ORM\Table'))->isSuperTypeOf($this->getType($current->var))->yes()) { + return null; + } + + $current->name = new Identifier('unhydratedFind'); + + return $node->var; + } + + $current = $current->var; + } + + return null; + } +} diff --git a/src/Rector/Set/CakePHPSetList.php b/src/Rector/Set/CakePHPSetList.php index 072b7dc9..cabb3545 100644 --- a/src/Rector/Set/CakePHPSetList.php +++ b/src/Rector/Set/CakePHPSetList.php @@ -85,6 +85,11 @@ final class CakePHPSetList */ public const CAKEPHP_53 = __DIR__ . '/../../../config/rector/sets/cakephp53.php'; + /** + * @var string + */ + public const CAKEPHP_54 = __DIR__ . '/../../../config/rector/sets/cakephp54.php'; + /** * @var string */ diff --git a/tests/TestCase/Command/RectorCommandTest.php b/tests/TestCase/Command/RectorCommandTest.php index 97bbb9d4..287f1131 100644 --- a/tests/TestCase/Command/RectorCommandTest.php +++ b/tests/TestCase/Command/RectorCommandTest.php @@ -123,6 +123,13 @@ public function testApply53() $this->assertTestAppUpgraded(); } + public function testApply54() + { + $this->setupTestApp(__FUNCTION__); + $this->exec('upgrade rector --rules cakephp54 ' . TEST_APP); + $this->assertTestAppUpgraded(); + } + public function testApplyMigrations45() { $this->setupTestApp(__FUNCTION__); diff --git a/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/DisableHydrationToUnhydratedFindRectorTest.php b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/DisableHydrationToUnhydratedFindRectorTest.php new file mode 100644 index 00000000..f73f0e79 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/DisableHydrationToUnhydratedFindRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/fixture.php.inc b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/fixture.php.inc new file mode 100644 index 00000000..d08f7f27 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/fixture.php.inc @@ -0,0 +1,39 @@ +find()->disableHydration(); + $articles->find('all')->disableHydration(); + $articles->find()->where(['id' => 1])->contain(['Users'])->disableHydration(); + $articles->find()->disableHydration()->toArray(); + } +} + +?> +----- +unhydratedFind(); + $articles->unhydratedFind('all'); + $articles->unhydratedFind()->where(['id' => 1])->contain(['Users']); + $articles->unhydratedFind()->toArray(); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_detached_query.php.inc b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_detached_query.php.inc new file mode 100644 index 00000000..bdb74cbe --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_detached_query.php.inc @@ -0,0 +1,21 @@ +disableHydration(); + + // Associations do not have an unhydratedFind() counterpart. + $articles->getAssociation('Users')->find()->disableHydration(); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_not_a_table.php.inc b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_not_a_table.php.inc new file mode 100644 index 00000000..15a71145 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/Fixture/skip_not_a_table.php.inc @@ -0,0 +1,27 @@ +find()->disableHydration(); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/config/configured_rule.php b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/config/configured_rule.php new file mode 100644 index 00000000..c8eb3bb1 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/DisableHydrationToUnhydratedFindRector/config/configured_rule.php @@ -0,0 +1,9 @@ +rule(DisableHydrationToUnhydratedFindRector::class); +}; diff --git a/tests/test_apps/original/RectorCommand-testApply54/src/SomeTest.php b/tests/test_apps/original/RectorCommand-testApply54/src/SomeTest.php new file mode 100644 index 00000000..144da379 --- /dev/null +++ b/tests/test_apps/original/RectorCommand-testApply54/src/SomeTest.php @@ -0,0 +1,20 @@ +fetchTable('Articles'); + $results = $table->find()->where(['published' => true])->disableHydration(); + } +} diff --git a/tests/test_apps/upgraded/RectorCommand-testApply54/src/SomeTest.php b/tests/test_apps/upgraded/RectorCommand-testApply54/src/SomeTest.php new file mode 100644 index 00000000..9e316606 --- /dev/null +++ b/tests/test_apps/upgraded/RectorCommand-testApply54/src/SomeTest.php @@ -0,0 +1,20 @@ +fetchTable('Articles'); + $results = $table->unhydratedFind()->where(['published' => true]); + } +}