From 3d9662c41e2ff1e3f027a7697bc0be3210310962 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 22 Jan 2026 08:34:09 +0100 Subject: [PATCH 1/2] Add BreadcrumbsHelper add/prepend => addMany/prependMany rector rule In CakePHP 5.3, passing an array of crumbs to BreadcrumbsHelper::add() or prepend() is deprecated. Use addMany() or prependMany() instead. The rector detects when the first argument is an array containing nested arrays (array of crumbs) vs a single crumb with title/url keys. --- config/rector/sets/cakephp53.php | 2 + .../BreadcrumbsHelperAddManyRector.php | 142 ++++++++++++++++++ .../BreadcrumbsHelperAddManyRectorTest.php | 28 ++++ .../Fixture/fixture.php.inc | 53 +++++++ .../Fixture/skip_single_crumb.php.inc | 24 +++ .../config/configured_rule.php | 9 ++ .../src/SomeTest.php | 10 ++ .../src/SomeTest.php | 10 ++ 8 files changed, 278 insertions(+) create mode 100644 src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php create mode 100644 tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php create mode 100644 tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc create mode 100644 tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc create mode 100644 tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php diff --git a/config/rector/sets/cakephp53.php b/config/rector/sets/cakephp53.php index 83c1c5d5..197dc3b5 100644 --- a/config/rector/sets/cakephp53.php +++ b/config/rector/sets/cakephp53.php @@ -2,6 +2,7 @@ declare(strict_types=1); use Cake\Upgrade\Rector\Rector\ClassMethod\FormExecuteToProcessRector; +use Cake\Upgrade\Rector\Rector\MethodCall\BreadcrumbsHelperAddManyRector; use Cake\Upgrade\Rector\Rector\MethodCall\EntityIsEmptyRector; use Cake\Upgrade\Rector\Rector\MethodCall\EntityPatchRector; use Cake\Upgrade\Rector\Rector\MethodCall\NewExprToFuncRector; @@ -24,6 +25,7 @@ 'Cake\TestSuite\Fixture\TransactionFixtureStrategy' => 'Cake\TestSuite\Fixture\TransactionStrategy', 'Cake\TestSuite\Fixture\TruncateFixtureStrategy' => 'Cake\TestSuite\Fixture\TruncateStrategy', ]); + $rectorConfig->rule(BreadcrumbsHelperAddManyRector::class); $rectorConfig->rule(EntityIsEmptyRector::class); $rectorConfig->rule(EntityPatchRector::class); $rectorConfig->rule(FormExecuteToProcessRector::class); diff --git a/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php b/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php new file mode 100644 index 00000000..ecfbc7fb --- /dev/null +++ b/src/Rector/Rector/MethodCall/BreadcrumbsHelperAddManyRector.php @@ -0,0 +1,142 @@ +Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], +]); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +$this->Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], +]); +CODE_SAMPLE, + ), + ], + ); + } + + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + public function refactor(Node $node): ?Node + { + if (!$node instanceof MethodCall) { + return null; + } + + // Must be add or prepend method + if (!$node->name instanceof Identifier) { + return null; + } + + $methodName = $node->name->toString(); + if ($methodName !== 'add' && $methodName !== 'prepend') { + return null; + } + + // Must have at least one argument + if (count($node->args) < 1) { + return null; + } + + // First argument must be an array + $firstArg = $node->args[0]->value; + if (!$firstArg instanceof Array_) { + return null; + } + + // Check if the array looks like an array of crumbs (array of arrays) + // rather than a single crumb with title/url keys + if (!$this->isArrayOfCrumbs($firstArg)) { + return null; + } + + // Check if this is called on BreadcrumbsHelper + $callerType = $this->getType($node->var); + if (!$callerType instanceof ObjectType) { + return null; + } + + if (!$callerType->isInstanceOf('Cake\View\Helper\BreadcrumbsHelper')->yes()) { + return null; + } + + // Rename to addMany or prependMany + $newMethodName = $methodName === 'add' ? 'addMany' : 'prependMany'; + $node->name = new Identifier($newMethodName); + + return $node; + } + + /** + * Determine if an array is an array of crumbs (array of arrays) + * vs a single crumb (array with title/url keys) + */ + private function isArrayOfCrumbs(Array_ $array): bool + { + // An array of crumbs is typically a numerically indexed array of arrays + // e.g. [['title' => 'Home'], ['title' => 'Articles']] + // A single crumb would have string keys like 'title', 'url' + // e.g. ['title' => 'Home', 'url' => '/'] + + if (count($array->items) === 0) { + return false; + } + + foreach ($array->items as $item) { + if (!$item instanceof ArrayItem) { + continue; + } + + // If item has a string key like 'title' or 'url', it's a single crumb + if ($item->key instanceof String_) { + $keyValue = $item->key->value; + if ($keyValue === 'title' || $keyValue === 'url' || $keyValue === 'options') { + return false; + } + } + + // If the item value is an array, it's likely an array of crumbs + if ($item->value instanceof Array_) { + return true; + } + } + + return false; + } +} diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.php new file mode 100644 index 00000000..34412c08 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/BreadcrumbsHelperAddManyRectorTest.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/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc new file mode 100644 index 00000000..7705a43b --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/fixture.php.inc @@ -0,0 +1,53 @@ +Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], + ]); + + // Should transform: prepend with array of crumbs + $this->Breadcrumbs->prepend([ + ['title' => 'Dashboard'], + ]); + } +} + +?> +----- +Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ['title' => 'Articles', 'url' => '/articles'], + ]); + + // Should transform: prepend with array of crumbs + $this->Breadcrumbs->prependMany([ + ['title' => 'Dashboard'], + ]); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc new file mode 100644 index 00000000..3b98de83 --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/Fixture/skip_single_crumb.php.inc @@ -0,0 +1,24 @@ +Breadcrumbs->add('Home', '/'); + + // Should NOT transform: single crumb as array with title key + $this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']); + + // Should NOT transform: prepend single crumb + $this->Breadcrumbs->prepend('Dashboard'); + } +} + +?> diff --git a/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php new file mode 100644 index 00000000..809158cc --- /dev/null +++ b/tests/TestCase/Rector/MethodCall/BreadcrumbsHelperAddManyRector/config/configured_rule.php @@ -0,0 +1,9 @@ +rule(BreadcrumbsHelperAddManyRector::class); +}; diff --git a/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php b/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php index e3d9c78b..ebd980b5 100644 --- a/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php +++ b/tests/test_apps/original/RectorCommand-testApply53/src/SomeTest.php @@ -6,11 +6,14 @@ use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; +use Cake\View\Helper\BreadcrumbsHelper; class SomeTest { use LocatorAwareTrait; + private BreadcrumbsHelper $Breadcrumbs; + public function testRenames(): void { $entity = new Entity(); @@ -22,6 +25,13 @@ public function testRenames(): void $table = $this->fetchTable('Articles'); $expr = $table->find()->newExpr(); + + // BreadcrumbsHelper::add(array) should be changed to addMany() + $this->Breadcrumbs->add([ + ['title' => 'Home', 'url' => '/'], + ]); + // Single crumb should stay as is + $this->Breadcrumbs->add('Articles', '/articles'); } public function findSomething(Query $query, array $options): Query { diff --git a/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php b/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php index 304c6187..f1342006 100644 --- a/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php +++ b/tests/test_apps/upgraded/RectorCommand-testApply53/src/SomeTest.php @@ -6,11 +6,14 @@ use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Query; +use Cake\View\Helper\BreadcrumbsHelper; class SomeTest { use LocatorAwareTrait; + private BreadcrumbsHelper $Breadcrumbs; + public function testRenames(): void { $entity = new Entity(); @@ -22,6 +25,13 @@ public function testRenames(): void $table = $this->fetchTable('Articles'); $expr = $table->find()->expr(); + + // BreadcrumbsHelper::add(array) should be changed to addMany() + $this->Breadcrumbs->addMany([ + ['title' => 'Home', 'url' => '/'], + ]); + // Single crumb should stay as is + $this->Breadcrumbs->add('Articles', '/articles'); } public function findSomething(\Cake\ORM\Query\SelectQuery $query, array $options): \Cake\ORM\Query\SelectQuery { From 6517f29463149f9675d1e0fbc12fb73c7e441459 Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 22 Jan 2026 08:42:24 +0100 Subject: [PATCH 2/2] Update CI to use PHP 8.2 (required by CakePHP 5.x) --- .github/workflows/ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2e11e7f..a28041ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1'] + php-version: ['8.2'] prefer-lowest: [''] include: - - php-version: '8.1' + - php-version: '8.2' prefer-lowest: 'prefer-lowest' steps: @@ -51,14 +51,12 @@ jobs: run: | if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then make install-dev-lowest - elif ${{ matrix.php-version == '8.1' }}; then - make install-dev-ignore-reqs else make install-dev fi - name: Setup problem matchers for PHPUnit - if: matrix.php-version == '8.1' + if: matrix.php-version == '8.2' run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Run PHPUnit @@ -74,7 +72,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' extensions: mbstring, intl tools: cs2pr coverage: none