5

In a node preprocess hook I am getting breadcrumbs for the currently viewed node by doing the following:

$vars['breadcrumb'] = Drupal::service('breadcrumb')
    ->build(Drupal::routeMatch())->toRenderable();

This works fine as it uses Drupal::routeMatch() to get the matched route for the current request.

But I also want to get the breadcrumb for each node in a listing of several nodes. In this case I cannot use Drupal::routeMatch() since I want to get the breadcrumb for each node in the listing, not the current page. The problem is I cannot find a way of getting a RouteMatchInterface object (needed by the BreadcrumbBuilder) from a node object. I can get the url containing route information via $node->urlInfo()but there seems to be no way of getting from there to a route match either.

How can this be done?

Edit:

I found a way to get the route match for a certain node. Not sure if it's the ideal way but it gets the job done:

$routeName = $node->urlInfo()->getRouteName();
$routeParameters = $node->urlInfo()->getRouteParameters();
$route = Drupal::service('router.route_provider')->getRouteByName($routeName);
$routeMatch = new RouteMatch($routeName, $route, $routeParameters);

However passing the $routeMatch into PathBasedBredcrumbBuilder::create does not generate bredcrumbs for that route!

On closer inspection it seems that the built-in PathBasedBreadCrumbBuilder class completely ignores the passed in $routeMatch parameter and instead builds the breadcrumbs using the path of the current page, fetched using the injected RequestContext object. I think I would need to swap out this service with my own implementation which uses the passed in parameters somehow. To be continued...

5 Answers 5

2

I eventually solved it by creating my own subclass of the PathBasedBredcrumbBuilder from core. This class unlike core's version actually uses the passed in route_match and returns breadcrumbs based on that which makes it possible to get breadcrumbs for a certain node (or any other given route).

I still need to calculate the route match from the current node to pass to the build method (as shown in the question). It feels a bit awkward since the route_match is converted back into a url object in the build method but it works and it is the cleanest solution i've come up with so far.

Below is the subclass:

namespace Drupal\contextual_breadcrumbs;

use Drupal\system\PathBasedBreadcrumbBuilder;
use Drupal\Core\Routing\RequestContext;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;

class ContextualPathBasedBreadcrumbBuilder extends PathBasedBreadcrumbBuilder {

    /**
    * {@inheritdoc}
    */
    public function build(RouteMatchInterface $route_match) {

      /// Set request context from passed in $route_match
      $url = Url::fromRouteMatch($route_match);
      if ($request = $this->getRequestForPath($url->toString(), [])) {
        $context = new RequestContext();
        $context->fromRequest($request);
        $this->context = $context;
      } 

      // Build breadcrumbs using new context ($route_match is unused)
      return parent::build($route_match);
    }

}
2
0

It seems you could use the service_collector to just add your custom ContextualPathBasedBreadcrumbBuilder.

For that just add in your modules services.yml:

  my_custom_module.breadcrumb:
    class: Drupal\ my_custom_module.breadcrumb\CustomBreadcrumbBuilder
    arguments: ['@router.request_context', '@access_manager', '@router', '@path_processor_manager', '@config.factory',  '@title_resolver', '@current_user', '@path.current', '@path.matcher']
    tags:
      - { name: breadcrumb_builder, priority: 1002 }

0

I'm not sure why but I had to amend the ContextualPathBasedBreadcrumbBuilder to following for it to work, it was complaining about missing node parameter before:

class ContextualPathBasedBreadcrumbBuilder extends PathBasedBreadcrumbBuilder {

  /**
   * {@inheritdoc}
   */
  public function build(RouteMatchInterface $route_match) {

    $url = Url::fromRoute($route_match->getRouteName(), $route_match->getParameters()->all());

    if ($request = $this->getRequestForPath($url->toString(), [])) {
      $context = new RequestContext();
      $context->fromRequest($request);
      $this->context = $context;
    }

    // Build breadcrumbs using new context ($route_match is unused)
    return parent::build($route_match);
  }

}
0

For Drupal 9 you don't need to extend anything. Working code here:

$routeName = $entity->toUrl()->getRouteName();
$routeParameters = $entity->toUrl()->getRouteParameters();
$route = Drupal::service('router.route_provider')->getRouteByName($routeName);
$routeMatch = new Drupal\Core\Routing\RouteMatch($routeName, $route, $routeParameters, $routeParameters);
$build['search_breadcrumb'] = Drupal::service('breadcrumb')->build($routeMatch)->toRenderable();
0

Build Breadcrumbs for a certain node in Drupal-9.

Code is running with Drupal-9:

$entity = \Drupal::entityTypeManager()->getStorage('node')->load($nid);

$routeName = $entity->toUrl()->getRouteName();
$routeParameters = $entity->toUrl()->getRouteParameters();
$route = \Drupal::service('router.route_provider')->getRouteByName($routeName);
$routeMatch = new \Drupal\Core\Routing\RouteMatch($routeName, $route, $routeParameters, $routeParameters);
$breadcrumb = \Drupal::service('breadcrumb')->build($routeMatch)->toRenderable();

dump($breadcrumb)

Output:

^ array:3 [▼
"#cache" => array:3 [▼
    "contexts" => array:3 [▼
      0 => "route"
      1 => "url.path"
      2 => "languages"
    ]
    "tags" => array:1 [▼
      0 => "config:easy_breadcrumb.settings"
    ]
    "max-age" => -1
  ]
  "#theme" => "breadcrumb"
  "#links" => array:1 [▼
    0 => Drupal\Core\Link {#4369 ▼
      #linkGenerator: null
      #text: "Home"
      #url: Drupal\Core\Url {#4752 ▼
        #urlGenerator: null
        #urlAssembler: null
        #accessManager: null
        #routeName: "<front>"
        #routeParameters: []
        #options: []
        #external: false
        #unrouted: false
        #uri: null
        #internalPath: null
        #_serviceIds: []
        #_entityStorages: []
      }
    }
  ]
]

Your Answer

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

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