Skip to content

Commit 0d6ecee

Browse files
committed
Add BreadcrumbsHelper title to content rector rule for CakePHP 6.0
1 parent a95a6f6 commit 0d6ecee

File tree

4 files changed

+281
-0
lines changed

4 files changed

+281
-0
lines changed

config/rector/sets/cakephp60.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22
declare(strict_types=1);
33

4+
use Cake\Upgrade\Rector\Cake6\BreadcrumbsHelperTitleToContentRector;
45
use Cake\Upgrade\Rector\Cake6\EventManagerOnRector;
56
use Cake\Upgrade\Rector\Cake6\RemoveAssignmentFromVoidMethodRector;
67
use Cake\Upgrade\Rector\Cake6\ReplaceCommandArgsIoWithPropertiesRector;
@@ -983,4 +984,8 @@
983984
// ConsoleInputOption::validateChoice() - returns void or throws ConsoleException
984985
new VoidMethod('Cake\Console\ConsoleInputOption', 'validateChoice'),
985986
]);
987+
988+
// BreadcrumbsHelper 'title' key/parameter renamed to 'content'
989+
// @see https://github.com/cakephp/cakephp/pull/18334
990+
$rectorConfig->rule(BreadcrumbsHelperTitleToContentRector::class);
986991
};
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Cake\Upgrade\Rector\Cake6;
5+
6+
use PhpParser\Node;
7+
use PhpParser\Node\Arg;
8+
use PhpParser\Node\Expr\Array_;
9+
use PhpParser\Node\Expr\ArrayItem;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PhpParser\Node\Identifier;
12+
use PhpParser\Node\Scalar\String_;
13+
use PHPStan\Type\ObjectType;
14+
use Rector\Rector\AbstractRector;
15+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
16+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
17+
18+
/**
19+
* Transforms BreadcrumbsHelper 'title' array keys and named parameters to 'content'
20+
*
21+
* In CakePHP 6.0, the 'title' key/parameter was renamed to 'content' for clarity.
22+
*
23+
* @see https://github.com/cakephp/cakephp/pull/18334
24+
*/
25+
final class BreadcrumbsHelperTitleToContentRector extends AbstractRector
26+
{
27+
/**
28+
* Methods where we should rename 'title' to 'content' in array keys
29+
*/
30+
private const METHODS_WITH_TITLE_ARRAY = [
31+
'add',
32+
'prepend',
33+
'addMany',
34+
'prependMany',
35+
];
36+
37+
/**
38+
* Methods where first parameter name changed from matchingTitle to matchingContent
39+
*/
40+
private const METHODS_WITH_MATCHING_TITLE = [
41+
'insertBefore',
42+
'insertAfter',
43+
];
44+
45+
public function getRuleDefinition(): RuleDefinition
46+
{
47+
return new RuleDefinition(
48+
'Change BreadcrumbsHelper "title" array key and parameter to "content"',
49+
[
50+
new CodeSample(
51+
<<<'CODE_SAMPLE'
52+
$this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']);
53+
$this->Breadcrumbs->addMany([
54+
['title' => 'Home', 'url' => '/'],
55+
['title' => 'Articles', 'url' => '/articles'],
56+
]);
57+
$this->Breadcrumbs->insertBefore('Home', 'Dashboard', '/dashboard');
58+
CODE_SAMPLE
59+
,
60+
<<<'CODE_SAMPLE'
61+
$this->Breadcrumbs->add(['content' => 'Home', 'url' => '/']);
62+
$this->Breadcrumbs->addMany([
63+
['content' => 'Home', 'url' => '/'],
64+
['content' => 'Articles', 'url' => '/articles'],
65+
]);
66+
$this->Breadcrumbs->insertBefore('Home', 'Dashboard', '/dashboard');
67+
CODE_SAMPLE,
68+
),
69+
],
70+
);
71+
}
72+
73+
/**
74+
* @return array<class-string<\PhpParser\Node>>
75+
*/
76+
public function getNodeTypes(): array
77+
{
78+
return [MethodCall::class];
79+
}
80+
81+
/**
82+
* @param \PhpParser\Node\Expr\MethodCall $node
83+
*/
84+
public function refactor(Node $node): ?Node
85+
{
86+
if (!$node->name instanceof Identifier) {
87+
return null;
88+
}
89+
90+
// Check if this is called on BreadcrumbsHelper
91+
if (!$this->isObjectType($node->var, new ObjectType('Cake\View\Helper\BreadcrumbsHelper'))) {
92+
return null;
93+
}
94+
95+
$methodName = $node->name->toString();
96+
$hasChanges = false;
97+
98+
// Handle methods where array keys need 'title' → 'content' rename
99+
if (in_array($methodName, self::METHODS_WITH_TITLE_ARRAY, true)) {
100+
$hasChanges = $this->renameArrayKeys($node) || $hasChanges;
101+
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
102+
}
103+
104+
// Handle insertBefore/insertAfter: matchingTitle → matchingContent named param
105+
if (in_array($methodName, self::METHODS_WITH_MATCHING_TITLE, true)) {
106+
$hasChanges = $this->renameNamedParameter($node, 'matchingTitle', 'matchingContent') || $hasChanges;
107+
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
108+
}
109+
110+
// Handle insertAt: title → content named param
111+
if ($methodName === 'insertAt') {
112+
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
113+
}
114+
115+
return $hasChanges ? $node : null;
116+
}
117+
118+
/**
119+
* Rename 'title' keys to 'content' in array arguments
120+
*/
121+
private function renameArrayKeys(MethodCall $node): bool
122+
{
123+
$hasChanges = false;
124+
125+
foreach ($node->args as $arg) {
126+
if (!$arg instanceof Arg) {
127+
continue;
128+
}
129+
130+
if ($arg->value instanceof Array_) {
131+
$hasChanges = $this->processArray($arg->value) || $hasChanges;
132+
}
133+
}
134+
135+
return $hasChanges;
136+
}
137+
138+
/**
139+
* Process an array, renaming 'title' keys to 'content'
140+
*/
141+
private function processArray(Array_ $array): bool
142+
{
143+
$hasChanges = false;
144+
145+
foreach ($array->items as $item) {
146+
if (!$item instanceof ArrayItem) {
147+
continue;
148+
}
149+
150+
// Rename 'title' key to 'content'
151+
if ($item->key instanceof String_ && $item->key->value === 'title') {
152+
$item->key = new String_('content');
153+
$hasChanges = true;
154+
}
155+
156+
// Recursively process nested arrays (for addMany/prependMany)
157+
if ($item->value instanceof Array_) {
158+
$hasChanges = $this->processArray($item->value) || $hasChanges;
159+
}
160+
}
161+
162+
return $hasChanges;
163+
}
164+
165+
/**
166+
* Rename a named parameter
167+
*/
168+
private function renameNamedParameter(MethodCall $node, string $oldName, string $newName): bool
169+
{
170+
foreach ($node->args as $arg) {
171+
if (!$arg instanceof Arg) {
172+
continue;
173+
}
174+
175+
if ($arg->name instanceof Identifier && $arg->name->toString() === $oldName) {
176+
$arg->name = new Identifier($newName);
177+
178+
return true;
179+
}
180+
}
181+
182+
return false;
183+
}
184+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace MyPlugin;
5+
6+
use Cake\View\Helper\BreadcrumbsHelper;
7+
8+
class BreadcrumbsExample
9+
{
10+
private BreadcrumbsHelper $Breadcrumbs;
11+
12+
public function testBreadcrumbs(): void
13+
{
14+
// Single crumb with title key should be changed to content
15+
$this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']);
16+
17+
// Multiple crumbs with title keys should be changed to content
18+
$this->Breadcrumbs->addMany([
19+
['title' => 'Home', 'url' => '/'],
20+
['title' => 'Articles', 'url' => '/articles'],
21+
]);
22+
23+
// Prepend with title key should be changed to content
24+
$this->Breadcrumbs->prepend(['title' => 'Dashboard', 'url' => '/dashboard']);
25+
26+
// PrependMany with title keys should be changed to content
27+
$this->Breadcrumbs->prependMany([
28+
['title' => 'Admin', 'url' => '/admin'],
29+
]);
30+
31+
// String argument should stay as is
32+
$this->Breadcrumbs->add('Contact', '/contact');
33+
34+
// Named parameter title should be changed to content
35+
$this->Breadcrumbs->add(title: 'About', url: '/about');
36+
37+
// insertBefore with named parameters
38+
$this->Breadcrumbs->insertBefore(matchingTitle: 'Home', title: 'Start', url: '/start');
39+
40+
// insertAfter with named parameters
41+
$this->Breadcrumbs->insertAfter(matchingTitle: 'Home', title: 'Next', url: '/next');
42+
43+
// insertAt with named parameter
44+
$this->Breadcrumbs->insertAt(0, title: 'First', url: '/first');
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace MyPlugin;
5+
6+
use Cake\View\Helper\BreadcrumbsHelper;
7+
8+
class BreadcrumbsExample
9+
{
10+
private BreadcrumbsHelper $Breadcrumbs;
11+
12+
public function testBreadcrumbs(): void
13+
{
14+
// Single crumb with title key should be changed to content
15+
$this->Breadcrumbs->add(['content' => 'Home', 'url' => '/']);
16+
17+
// Multiple crumbs with title keys should be changed to content
18+
$this->Breadcrumbs->addMany([
19+
['content' => 'Home', 'url' => '/'],
20+
['content' => 'Articles', 'url' => '/articles'],
21+
]);
22+
23+
// Prepend with title key should be changed to content
24+
$this->Breadcrumbs->prepend(['content' => 'Dashboard', 'url' => '/dashboard']);
25+
26+
// PrependMany with title keys should be changed to content
27+
$this->Breadcrumbs->prependMany([
28+
['content' => 'Admin', 'url' => '/admin'],
29+
]);
30+
31+
// String argument should stay as is
32+
$this->Breadcrumbs->add('Contact', '/contact');
33+
34+
// Named parameter title should be changed to content
35+
$this->Breadcrumbs->add(content: 'About', url: '/about');
36+
37+
// insertBefore with named parameters
38+
$this->Breadcrumbs->insertBefore(matchingContent: 'Home', content: 'Start', url: '/start');
39+
40+
// insertAfter with named parameters
41+
$this->Breadcrumbs->insertAfter(matchingContent: 'Home', content: 'Next', url: '/next');
42+
43+
// insertAt with named parameter
44+
$this->Breadcrumbs->insertAt(0, content: 'First', url: '/first');
45+
}
46+
}

0 commit comments

Comments
 (0)