18

I've tried to define a new breadcrumb override, but I'm still getting the site default.

I've created a custom module, foo_breadcrumb:

   - modules/custom/foo_breadcrumb
     - foo_breadcrumb.info.yml
     - foo_breadcrumb.services.yml
     - src/
         - BreadcrumbBuild.php

Here's the foo_breadcrumb.services.yml:

services:
    foo_breadcrumb.breadcrumb:
        class: Drupal\foo_breadcrumb\BreadcrumbBuild
        tags:
            - { name: breadcrumb_builder, priority: 100 }

Inside src/BreadcrumbBuild.php, I have:

<?php

namespace Drupal\foo_breadcrumb;

use Drupal\Core\Breadcrumb\BreadcrumbBuilderBase;

class BreadcrumbBuild implements BreadcrumbManager {
    /**
     * {@inheritdoc}
     */
    public function applies(array $attributes) {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function build(array $attributes) {
        $breadcrumb[] = $this->l($this->t('Test'), NULL);
        $breadcrumb[] = $this->l($this->t('Test2'), 'test');
        return $breadcrumb;
    }
}
?>

I started working off the only writeup I could find on Drupal 8 breadcrumbs, but the thing is it appears to be using an older version of the PSR-4 autoloading that is no longer in place (for the record I'm on 8.0.0-dev-beta3), and so I went by how all the other modules work in the codebase.

Now I'm fairly certain this correct to get the module to load; however I'm not sure if

class BreadcrumbBuild extends BreadcrumbBuilderBase

is correct. The problem is that the old tutorial I linked to mentions extending from BreadcrumbBuilderBase, but the more current docs don't seem to mention it and I wonder if it's out of date - and how I should do this.

Likewise, I don't really understand what the services.yml file is doing in this regard, there's no documentation anywhere for this.

8 Answers 8

8

Yeah breadcrumb changed and the documentation must be update.

Likewise, I don't really understand what the services.yml file is doing in this regard, there's no documentation anywhere for this.

For Drupal 8: The Crash Course | DrupalCon Amsterdam 2014, awesome presentation, about 47:02:

Drupal 8 in 2 steps:

  1. Build a tool
  2. Wire it up

The wiring may vary, the approach is the same.

How we "Wire it up" the breadcrumb:

For http://www.palantir.net/blog/d8ftw-breadcrumbs-work:

Now we need to tell the system about our class. To do that, we define a new service (remember those?) referencing our new class. We'll do that in our *.services.yml file, which exists for exactly this purpose

Similar to an "info hook" in previous Drupal versions, we're defining a service named mymodule.breadcrumb. It will be an instance of our breadcrumb class. If necessary we could pass arguments to our our class's constructor as well. Importantly, though, we also tag the service. Tagged services are a feature of the Symfony DependencyInjection component specifically and tell the system to automatically connect our builder to the breadcrumb manager. The priority specifies in what order various builders should be called, highest first. In case two applies() methods might both return true, whichever builder has the higher priority will be used and the other ignored.

You can use this code for you aim:

Structure (not matter much):

- modules/custom/foo_breadcrumb
  - foo_breadcrumb.info.yml
  - foo_breadcrumb.services.yml
  - src/
    - Breadcrumb/
      - BlogBreadcrumbBuilder.php

foo_breadcrumb.services.yml:

services:
  foo_breadcrumb.breadcrumb_blog:
    class: Drupal\foo_breadcrumb\Breadcrumb\BlogBreadcrumbBuilder
    tags:
      - { name: breadcrumb_builder, priority: 100 }

BlogBreadcrumbBuilder.php:

class BlogBreadcrumbBuilder implements BreadcrumbBuilderInterface {
  use StringTranslationTrait;
  use LinkGeneratorTrait;

  /**
   * @inheritdoc
   */
  public function applies(RouteMatchInterface $route_match) {
    // This breadcrumb apply only for all articles
    $parameters = $route_match->getParameters()->all();
    if (isset($parameters['node'])) {
      return $parameters['node']->getType() == 'article';
    }
  }

  /**
   * @inheritdoc
   */
  public function build(RouteMatchInterface $route_match) {
    $breadcrumb = [Link::createFromRoute($this->t('Home'), '<front>')];
    $breadcrumb[] = Link::createFromRoute($this->t('Blog'), '<<<your route for blog>>>');
    return $breadcrumb;
  }
}

Remember, clear cache at the end.

9
  • No joy so far. I actually copied taxonomy in core as closely as possible as this had a working implementation (I can call dpm('Test') from the applies() method and it'll output. But not so in my code; not even intentional syntax errors appear - which makes me suspect the service routing isn't correct. But my yaml is valid... sigh :(
    – njp
    Commented Mar 11, 2015 at 18:27
  • 1
    @njp review the path of you Breadcrumb class (I add a folder Breadcrumb) and it must match with "class" parameter on you service file. Too check for all names of class, sometime dont match in some file or parameters.
    – rpayanm
    Commented Mar 11, 2015 at 18:34
  • 1
    Congratulation! A one question: did you "clear cache" after of the modifications? Perhaps it could be that.
    – rpayanm
    Commented Mar 11, 2015 at 19:18
  • 1
    Yes, you need to clear the cache, that should be enough to update the services. Also, one that might be worth mentioning is the priority. The first builder that returns TRUE from applies() is going to win, so you might need to search for other services with that tag (which is an easy text search) and check their weight and applies() implementations.
    – Berdir
    Commented Mar 11, 2015 at 21:51
  • 1
    @njp on the contrary, the priority specifies in what order various builders should be called, highest first. In case two applies() methods might both return true, whichever builder has the higher priority will be used and the other ignored.
    – rpayanm
    Commented Mar 12, 2015 at 12:34
10

Here we go again. These answers are mostly right. One thing you can't forget about is "cache tags" and "cache contexts".

I was setting up a taxonomy term on a node as a breadcrumb.

I got it working with advice from this post, but then I clicked around and noticed the same breadcrumbs on every page.

Long story short, make sure to set some cache contexts and tags.

Here's my service in a gist: https://gist.github.com/jonpugh/ccaeb01e173abbc6c88f7a332d271e4a

Here's my build() method:

/**
 * {@inheritdoc}
 */
public function build(RouteMatchInterface $route_match) {
  $node = $route_match->getParameter('node');
  $breadcrumb = new Breadcrumb();

  // By setting a "cache context" to the "url", each requested URL gets it's own cache.
  // This way a single breadcrumb isn't cached for all pages on the site.
  $breadcrumb->addCacheContexts(["url"]);

  // By adding "cache tags" for this specific node, the cache is invalidated when the node is edited.
  $breadcrumb->addCacheTags(["node:{$node->nid->value}"]);

  // Add "Home" breadcrumb link.
  $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));

  // Given we have a taxonomy term reference field named "field_section", and that field has data,
  // Add that term as a breadcrumb link.
  if (!empty($node->field_section->entity)) {
    $breadcrumb->addLink($node->field_section->entity->toLink());
  }
  return $breadcrumb;
}
1
  • This caching issue was driving me nuts and a lot of the information online in blogs etc seems to miss this point - thank you!
    – kbrinner
    Commented Feb 28, 2019 at 19:44
8

Update 2016 Drupal 8

The Documentation states that you must return an instance of the breadcrumb class. If you are having trouble getting it to work. here is the solution that worked for me.

<?php

//modules/MY_MODULE/src/MyBreadcrumbBuilder.php

namespace Drupal\registration;

use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Link;

class MyBreadcrumbBuilder implements BreadcrumbBuilderInterface {

    /**
     * @inheritdoc
     */
    public function applies(RouteMatchInterface $route_match) {
        /* Allways use this. Change this is another module needs to use a new custom breadcrumb */
        return true;
        /* This code allows for only the registration page to get used by this breadcrumb
         * $parameters = explode('.', $route_match->getRouteName());
         * if ($parameters[0] === 'registration') {
         *     return true;
         * } else {
         *     return false;
         * }
         */
    }

    /**
     * @inheritdoc
     */
    public function build(RouteMatchInterface $route_match) {
        $parameters = explode('.', $route_match->getRouteName());
        $b = new Breadcrumb();
        if ($parameters[0] === 'registration') {
            /* If registration page use these links */
            $b->setLinks($this->buildRegistration($parameters[1]));
        }
        return $b;
    }

    /**
     * Creates all the links for the registration breadcrumb
     * @param type $page
     * @return type
     */
    private function buildRegistration($page) {
        return [
            Link::createFromRoute(t('Step One'), 'registration.one'),
            Link::createFromRoute(t('Step Two'), 'registration.two'),
            Link::createFromRoute(t('Step Three'), 'registration.three'),
            Link::createFromRoute(t('Step Four'), 'registration.four'),
            Link::createFromRoute(t('Step Five'), 'registration.five'),
            Link::createFromRoute(t('Step Six'), 'registration.six'),
            Link::createFromRoute(t('Step Seven'), 'registration.seven')
        ];
    }

}

Then the yml file

# modules/MY_MODULE/registration/MY_MODULE.services.yml
services:
  registration.breadcrumb:
    class: Drupal\registration\MyBreadcrumbBuilder
    tags:
      - { name: breadcrumb_builder, priority: 100 }

PS: if you are using bootstrap go to your /admin/appearance/settings settings page and look at the breadcrumbs settings. Show 'Home' breadcrumb link should be checked on. And Show current page title at end should be checked off.

After all this is done clear your cache. Everytime you change a YML file, even in debug mode, you need to clear your cache. you can go to /core/rebuild.php if you get stuck and can't rebuild.

7

Don't forget caching

The render cache was changed fairly late in the D8 development cycle, and so it's not mentioned in the d8ftw series, or the other answers to this question.

The Cache API documentation refers specifically to render arrays, but all of those instructions apply equally to Breadcrumbs. Breadcrumbs have a toRenderable() method, Drupal will try to cache them in the render cache, and that means you must specify enough information to allow Drupal to do it properly.

The details are in the docs, but the short version is that Breadcrumb implements the RefinableCachableDependencyInterface. In your builder class, you'll want to call addCachableDependency() with any and all entities or configuration objects that are used to build the breadcrumb. The documentation for 'CacheableDependencyInterface & friends' goes into more detail of how and why.

If there are other contexts where the breadcrumb might change, you'll also need to manually use addCacheContexts() to make sure the block varies, addCacheTags() to make sure the cache entry can be correctly invalidated, and mergeCacheMaxAge() if the cache is time-sensitive and needs to expire.

If this isn't done properly, one of your custom Breadcrumb builder services will 'win', and the breadcrumbs for that one specific page will be served on every page, to all visitors, forever.

5

There is another way to achieve this.

/**
 * Implements hook_preprocess_breadcrumb().
 */
 function theme_name_preprocess_breadcrumb(&$variables){
  if(($node = \Drupal::routeMatch()->getParameter('node')) && $variables['breadcrumb']){
    $variables['breadcrumb'][] = array(
     'text' => $node->getTitle() 
   );
  }
}

And then create another file in your theme's template folder named as "breadcrumb.html.twig" and put below code in this file :

{% if breadcrumb %}
  <nav class="breadcrumb" role="navigation" aria-labelledby="system-breadcrumb">
    <h2 id="system-breadcrumb" class="visually-hidden">{{ 'Breadcrumb'|t }}</h2>
    <ul>
    {% for item in breadcrumb %}
      <li>
        {% if item.url %}
          <a href="{{ item.url }}">{{ item.text }}</a>
        {% else %}
          {{ item.text }}
        {% endif %}
      </li> /
    {% endfor %}
    </ul>
  </nav>
{% endif %}

Thats it. Now flush the cache and you will get breadcrumb with current page title like Home/Current Page Title. You can change the separator by replacing "/" with the desired one.

1
  • This is a perfectly valid way to do this as well, if you are following a more legacy approach, which is fine to do. It has the virtue of having about 90% less boilerplate than the more up-to-date D8/D9 approach. However, you need to add a cache context inside your preprocessor's if{} statement, or your breadcrumb will end up being the same across every page in the site (if you have caching on). $variables['#cache']['contexts'][] = 'route';
    – apotek
    Commented Mar 17, 2022 at 4:30
2

You should use a contrib module to add the current page title to the breadcrumb such as Current Page Crumb: https://www.drupal.org/project/current_page_crumb

If wanted to hand-code it, you can pull the code from the src folder of that module. You can find more details about Drupal 8 breadcrumbs here: http://www.gregboggs.com/drupal8-breadcrumbs/

2
  • It is so frustrating that something as simple as this has to require grabbing contrib modules to add it in.
    – Kevin
    Commented Mar 25, 2016 at 18:43
  • Such is the Drupal way. Although Drupal 8 now does a TON in core that Drupal 7 never did. I'd fix core Drupal 8 breadcrumbs in core if I could. But, drush en current_page_crumb isn't so bad.
    – Greg Boggs
    Commented May 31, 2016 at 17:25
1

One of best guide at https://www.drupal.org/node/2876622

Specifically caching is missing in many of the answers which should be added in the build function

// Don't forget to add cache control by a route.
// Otherwise all pages will have the same breadcrumb.
$breadcrumb->addCacheContexts(['route']);
0

I had used Custom Breadcrumbs using token in Drupal 7 and when that module was not available for Drupal 8 I ended up creating views for my individual content types using the fields that were originally the token fields. Using it as a block and disabling the normal breadcrumb. It was a bit more work than Custom Breadcrumbs but it works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.