How do you implement a breadcrumb in Drupal 8?

by njp   Last Updated March 06, 2016 01:03 AM

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.

Tags : 8 breadcrumbs


Answers 7


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.

rpayanm
rpayanm
March 11, 2015 13:38 PM

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.

Neoaptt
Neoaptt
February 01, 2016 20:10 PM

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.

Sean C.
Sean C.
February 04, 2016 03:27 AM

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/

Greg Boggs
Greg Boggs
March 06, 2016 02:34 AM

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;
}
Jon Pugh
Jon Pugh
May 05, 2016 00:37 AM

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.

Sachin
Sachin
June 07, 2016 05:07 AM

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.

weben
weben
April 04, 2017 14:53 PM

Related Questions


Remove href from last breadcrumb

Updated August 06, 2015 17:03 PM

Drupal 7 easy breadcrumb module hides breadcrumbs

Updated April 20, 2015 21:03 PM

How to edit breadcrumb in drupal 7?

Updated May 13, 2015 12:40 PM

Node displaying in Easybreadcrumb

Updated March 02, 2017 13:07 PM

Multilingual Breadcrumb

Updated March 03, 2017 09:07 AM