I raised an issue on Github thinking it was a bug, but apparently it’s a feature. Now I have a follow-up question (for learning purposes) which I decided to ask here so that I don’t clutter Github.
The original issue
I work on a custom BreadcrumbsHelper intended to be a drop-in replacement of the Core counterpart.
class BreadcrumbsHelper extends \Cake\View\Helper\BreadcrumbsHelper
{
/**
* @inheritDoc
*/
protected array $_defaultConfig = [
'ariaCurrent' => 'last',
'templates' => [
'wrapper' => '<nav class="breadcrumbs container" aria-label="breadcrumb"><ol{{attrs}}>{{content}}</ol></nav>',
'item' => '<li{{attrs}}><a href="{{url}}"{{innerAttrs}}>{{title}}</a></li>',
'itemWithoutLink' => '<li{{attrs}}>{{title}}</li>',
'separator' => '',
],
];
/**
* @inheritDoc
*/
public function add(array|string $title, array|string|null $url = null, array $options = [])
{
$options = $this->addClass($options, 'breadcrumbs__list-item');
$innerAttrs = $options['innerAttrs'] ?? [];
$innerAttrs = $this->addClass($innerAttrs, 'breadcrumbs__link');
$options['innerAttrs'] = $innerAttrs;
return parent::add($title, $url, $options);
}
/**
* @inheritDoc
*/
public function render(array $attributes = [], array $separator = []): string
{
$attributes = $this->addClass($attributes, 'breadcrumbs__list');
return parent::render($attributes, $separator);
}
}
To test it, I use this (copied from some Book example):
$this->Breadcrumbs->add([
['title' => 'Products', 'url' => ['controller' => 'products', 'action' => 'index']],
['title' => 'Product name', 'url' => ['controller' => 'products', 'action' => 'view', 1234]],
]);
I expect the elements it renders to have the required CSS classes I added in my wrapper, but that doesn’t happen:
<nav class="breadcrumbs container" aria-label="breadcrumb">
<ol class="breadcrumbs__list">
<li><a href="/products">Products</a></li>
<li><a href="/products/view/1234">Product name</a></li>
</ol>
</nav>
This seems to be because the line 83 condition is true, so $options are defined empty on line 85:
Some ways around this in my add() wrapper could be:
- detect if
$titleis an array; copy and inject$optionsinto each crumb - detect if
$titleis an array; loop through each crumb; callparent::add()on the crumb individually with the options I need
None of this seems like an efficient approach. Is there any reason not to reuse the existing $options from the public function add declaration?
In other words,
public function add(array|string $title, array|string|null $url = null, array $options = []) // <---
{
if (is_array($title)) {
foreach ($title as $crumb) {
$this->crumbs[] = $crumb + ['title' => '', 'url' => null, 'options' => $options]; // <---
}
return $this;
}
// ...
The answer
If $title is an array then options should be passed within each item. Update your overriding method accordingly.
github.com/cakephp/cakephp/issues/19127#issuecomment-3656342400
My followup question
I understand that sometimes a breadcrumb needs to have its own options.
But if options are the same for all breadcrumbs, isn’t it more DRY to allow passing $options once and having them apply to all breadcrumbs? Especially if $options default to [] in the function declaration anyway - so if developer does nothing, each breadcrumb ends up with [] anyway.
Do breadcrumbs need individual options predominantly often, to the point it doesn’t make sense to support shared $options?
Thank you for reading?