From 0d6ecee616f75338fe0aae2dd75a5a9ad3f9f3dc Mon Sep 17 00:00:00 2001 From: mscherer Date: Thu, 5 Feb 2026 08:40:10 +0100 Subject: [PATCH] Add BreadcrumbsHelper title to content rector rule for CakePHP 6.0 --- config/rector/sets/cakephp60.php | 5 + .../BreadcrumbsHelperTitleToContentRector.php | 184 ++++++++++++++++++ .../src/BreadcrumbsExample.php | 46 +++++ .../src/BreadcrumbsExample.php | 46 +++++ 4 files changed, 281 insertions(+) create mode 100644 src/Rector/Cake6/BreadcrumbsHelperTitleToContentRector.php create mode 100644 tests/test_apps/original/RectorCommand-testApply60/src/BreadcrumbsExample.php create mode 100644 tests/test_apps/upgraded/RectorCommand-testApply60/src/BreadcrumbsExample.php diff --git a/config/rector/sets/cakephp60.php b/config/rector/sets/cakephp60.php index 416d96a..d8327ca 100644 --- a/config/rector/sets/cakephp60.php +++ b/config/rector/sets/cakephp60.php @@ -1,6 +1,7 @@ rule(BreadcrumbsHelperTitleToContentRector::class); }; diff --git a/src/Rector/Cake6/BreadcrumbsHelperTitleToContentRector.php b/src/Rector/Cake6/BreadcrumbsHelperTitleToContentRector.php new file mode 100644 index 0000000..5f924f2 --- /dev/null +++ b/src/Rector/Cake6/BreadcrumbsHelperTitleToContentRector.php @@ -0,0 +1,184 @@ +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> + */ + 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; + } +} diff --git a/tests/test_apps/original/RectorCommand-testApply60/src/BreadcrumbsExample.php b/tests/test_apps/original/RectorCommand-testApply60/src/BreadcrumbsExample.php new file mode 100644 index 0000000..0e63b11 --- /dev/null +++ b/tests/test_apps/original/RectorCommand-testApply60/src/BreadcrumbsExample.php @@ -0,0 +1,46 @@ +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'); + } +} diff --git a/tests/test_apps/upgraded/RectorCommand-testApply60/src/BreadcrumbsExample.php b/tests/test_apps/upgraded/RectorCommand-testApply60/src/BreadcrumbsExample.php new file mode 100644 index 0000000..78399fe --- /dev/null +++ b/tests/test_apps/upgraded/RectorCommand-testApply60/src/BreadcrumbsExample.php @@ -0,0 +1,46 @@ +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'); + } +}