Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 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\Cake6\BreadcrumbsHelperTitleToContentRector;
use Cake\Upgrade\Rector\Cake6\EventManagerOnRector;
use Cake\Upgrade\Rector\Cake6\RemoveAssignmentFromVoidMethodRector;
use Cake\Upgrade\Rector\Cake6\ReplaceCommandArgsIoWithPropertiesRector;
Expand Down Expand Up @@ -983,4 +984,8 @@
// ConsoleInputOption::validateChoice() - returns void or throws ConsoleException
new VoidMethod('Cake\Console\ConsoleInputOption', 'validateChoice'),
]);

// BreadcrumbsHelper 'title' key/parameter renamed to 'content'
// @see https://github.com/cakephp/cakephp/pull/18334
$rectorConfig->rule(BreadcrumbsHelperTitleToContentRector::class);
};
184 changes: 184 additions & 0 deletions src/Rector/Cake6/BreadcrumbsHelperTitleToContentRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);

namespace Cake\Upgrade\Rector\Cake6;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PHPStan\Type\ObjectType;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* Transforms BreadcrumbsHelper 'title' array keys and named parameters to 'content'
*
* In CakePHP 6.0, the 'title' key/parameter was renamed to 'content' for clarity.
*
* @see https://github.com/cakephp/cakephp/pull/18334
*/
final class BreadcrumbsHelperTitleToContentRector extends AbstractRector
{
/**
* Methods where we should rename 'title' to 'content' in array keys
*/
private const METHODS_WITH_TITLE_ARRAY = [
'add',
'prepend',
'addMany',
'prependMany',
];

/**
* Methods where first parameter name changed from matchingTitle to matchingContent
*/
private const METHODS_WITH_MATCHING_TITLE = [
'insertBefore',
'insertAfter',
];

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Change BreadcrumbsHelper "title" array key and parameter to "content"',
[
new CodeSample(
<<<'CODE_SAMPLE'
$this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']);
$this->Breadcrumbs->addMany([
['title' => 'Home', 'url' => '/'],
['title' => 'Articles', 'url' => '/articles'],
]);
$this->Breadcrumbs->insertBefore('Home', 'Dashboard', '/dashboard');
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$this->Breadcrumbs->add(['content' => 'Home', 'url' => '/']);
$this->Breadcrumbs->addMany([
['content' => 'Home', 'url' => '/'],
['content' => 'Articles', 'url' => '/articles'],
]);
$this->Breadcrumbs->insertBefore('Home', 'Dashboard', '/dashboard');
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
{
if (!$node->name instanceof Identifier) {
return null;
}

// Check if this is called on BreadcrumbsHelper
if (!$this->isObjectType($node->var, new ObjectType('Cake\View\Helper\BreadcrumbsHelper'))) {
return null;
}

$methodName = $node->name->toString();
$hasChanges = false;

// Handle methods where array keys need 'title' → 'content' rename
if (in_array($methodName, self::METHODS_WITH_TITLE_ARRAY, true)) {
$hasChanges = $this->renameArrayKeys($node) || $hasChanges;
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
}

// Handle insertBefore/insertAfter: matchingTitle → matchingContent named param
if (in_array($methodName, self::METHODS_WITH_MATCHING_TITLE, true)) {
$hasChanges = $this->renameNamedParameter($node, 'matchingTitle', 'matchingContent') || $hasChanges;
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
}

// Handle insertAt: title → content named param
if ($methodName === 'insertAt') {
$hasChanges = $this->renameNamedParameter($node, 'title', 'content') || $hasChanges;
}

return $hasChanges ? $node : null;
}

/**
* Rename 'title' keys to 'content' in array arguments
*/
private function renameArrayKeys(MethodCall $node): bool
{
$hasChanges = false;

foreach ($node->args as $arg) {
if (!$arg instanceof Arg) {
continue;
}

if ($arg->value instanceof Array_) {
$hasChanges = $this->processArray($arg->value) || $hasChanges;
}
}

return $hasChanges;
}

/**
* Process an array, renaming 'title' keys to 'content'
*/
private function processArray(Array_ $array): bool
{
$hasChanges = false;

foreach ($array->items as $item) {
if (!$item instanceof ArrayItem) {
continue;
}

// Rename 'title' key to 'content'
if ($item->key instanceof String_ && $item->key->value === 'title') {
$item->key = new String_('content');
$hasChanges = true;
}

// Recursively process nested arrays (for addMany/prependMany)
if ($item->value instanceof Array_) {
$hasChanges = $this->processArray($item->value) || $hasChanges;
}
}

return $hasChanges;
}

/**
* Rename a named parameter
*/
private function renameNamedParameter(MethodCall $node, string $oldName, string $newName): bool
{
foreach ($node->args as $arg) {
if (!$arg instanceof Arg) {
continue;
}

if ($arg->name instanceof Identifier && $arg->name->toString() === $oldName) {
$arg->name = new Identifier($newName);

return true;
}
}

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

namespace MyPlugin;

use Cake\View\Helper\BreadcrumbsHelper;

class BreadcrumbsExample
{
private BreadcrumbsHelper $Breadcrumbs;

public function testBreadcrumbs(): void
{
// Single crumb with title key should be changed to content
$this->Breadcrumbs->add(['title' => 'Home', 'url' => '/']);

// Multiple crumbs with title keys should be changed to content
$this->Breadcrumbs->addMany([
['title' => 'Home', 'url' => '/'],
['title' => 'Articles', 'url' => '/articles'],
]);

// Prepend with title key should be changed to content
$this->Breadcrumbs->prepend(['title' => 'Dashboard', 'url' => '/dashboard']);

// PrependMany with title keys should be changed to content
$this->Breadcrumbs->prependMany([
['title' => 'Admin', 'url' => '/admin'],
]);

// String argument should stay as is
$this->Breadcrumbs->add('Contact', '/contact');

// Named parameter title should be changed to content
$this->Breadcrumbs->add(title: 'About', url: '/about');

// insertBefore with named parameters
$this->Breadcrumbs->insertBefore(matchingTitle: 'Home', title: 'Start', url: '/start');

// insertAfter with named parameters
$this->Breadcrumbs->insertAfter(matchingTitle: 'Home', title: 'Next', url: '/next');

// insertAt with named parameter
$this->Breadcrumbs->insertAt(0, title: 'First', url: '/first');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);

namespace MyPlugin;

use Cake\View\Helper\BreadcrumbsHelper;

class BreadcrumbsExample
{
private BreadcrumbsHelper $Breadcrumbs;

public function testBreadcrumbs(): void
{
// Single crumb with title key should be changed to content
$this->Breadcrumbs->add(['content' => 'Home', 'url' => '/']);

// Multiple crumbs with title keys should be changed to content
$this->Breadcrumbs->addMany([
['content' => 'Home', 'url' => '/'],
['content' => 'Articles', 'url' => '/articles'],
]);

// Prepend with title key should be changed to content
$this->Breadcrumbs->prepend(['content' => 'Dashboard', 'url' => '/dashboard']);

// PrependMany with title keys should be changed to content
$this->Breadcrumbs->prependMany([
['content' => 'Admin', 'url' => '/admin'],
]);

// String argument should stay as is
$this->Breadcrumbs->add('Contact', '/contact');

// Named parameter title should be changed to content
$this->Breadcrumbs->add(content: 'About', url: '/about');

// insertBefore with named parameters
$this->Breadcrumbs->insertBefore(matchingContent: 'Home', content: 'Start', url: '/start');

// insertAfter with named parameters
$this->Breadcrumbs->insertAfter(matchingContent: 'Home', content: 'Next', url: '/next');

// insertAt with named parameter
$this->Breadcrumbs->insertAt(0, content: 'First', url: '/first');
}
}
Loading