Drupal - How do I alter the route defined by another module?

Allow views to override existing routing items just uses existing functionality.

If you want to change the information attached to a route, not the menu, like the actual used controller, or the requirements (permission/role etc.) you can use an event provided by Drupal:

  <?php

  use Drupal\Core\Routing\RouteBuildEvent;
  use Drupal\Core\Routing\RoutingEvents;
  use Symfony\Component\EventDispatcher\EventSubscriberInterface;

  class RouteSubscriber implements EventSubscriberInterface {
 
   /**
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
      $events[RoutingEvents::ALTER] = 'alterRoutes';
     return $events;
   }

  /**
   * Alters existing routes.
   *
   * @param \Drupal\Core\Routing\RouteBuildEvent $event
   *   The route building event.
   */
  protected function alterRoutes(RouteBuildEvent $event) {
    // Fetch the collection which can be altered.
    $collection = $event->getRouteCollection();
    // The event is fired multiple times so ensure that the user_page route
    // is available.
    if ($route = $collection->get('user_page')) {
      // As example add a new requirement.
      $route->setRequirement('_role', 'anonymous');
    }
  }

  }

Additionally you have to register a service with the tag 'event_subscriber' for this class.


Since I asked this question, Drupal 8 core changed, and some of the issues about routes have been fixed.

hook_menu() is not used from Drupal 8 anymore; the routes a module uses are defined in a .routing.yml file (e.g. user.routing.yml). Alter hooks are still used, but since hook_menu() is not used anymore from Drupal core, Drupal core doesn't invoke hook_menu_alter() too.

The steps necessary to alter a route defined from other modules are the following:

  • Define a service tagged as event_subscriber. (The tag is the important part, or the service would not work as expected.)

      services:
        mymodule.route_subscriber:
          class: Drupal\mymodule\Routing\RouteSubscriber
          tags:
            - { name: event_subscriber }
    
  • Create a class that extends RouteSubscriberBase.

      namespace Drupal\mymodule\Routing;
    
      use Drupal\Core\Routing\RouteSubscriberBase;
      use Symfony\Component\Routing\RouteCollection;
    
      /**
       * Listens to the dynamic route events.
       */
      class RouteSubscriber extends RouteSubscriberBase {
    
        /**
         * {@inheritdoc}
         */
        protected function alterRoutes(RouteCollection $collection) {
          // Change the route associated with the user profile page (/user, /user/{uid}).
          if ($route = $collection->get('user.page')) {
            $route->setDefault('_controller', '\Drupal\mymodule\Controller\UserController::userPage');
          }
        }
    
      }
    

    For real implementation examples of alterRoutes(), see AdminRouteSubscriber::alterRoutes() or RouteSubscriber::alterRoutes() .

    protected function alterRoutes(RouteCollection $collection) {
      foreach ($collection->all() as $route) {
        if (strpos($route->getPath(), '/admin') === 0 && !$route->hasOption('_admin_route') && static::isHtmlRoute($route)) {
          $route->setOption('_admin_route', TRUE);
        }
      }
    }
    
    protected function alterRoutes(RouteCollection $collection) {
      $mappers = $this->mapperManager->getMappers($collection);
      foreach ($mappers as $mapper) {
        $collection->add($mapper->getOverviewRouteName(), $mapper->getOverviewRoute());
        $collection->add($mapper->getAddRouteName(), $mapper->getAddRoute());
        $collection->add($mapper->getEditRouteName(), $mapper->getEditRoute());
        $collection->add($mapper->getDeleteRouteName(), $mapper
          ->getDeleteRoute());
      }
    }
    

Notice that compared with earlier Drupal 8 releases, some details are changed.

  • The route name is changed from user_page to user.page
  • The requirements for that route changed from _access: 'TRUE' to _user_is_logged_in: 'TRUE'
  • The property to set the controller of a route changed from _content to _controller

See Altering existing routes and adding new routes based on dynamic ones; it worked nicely for me to remove /search handled by the Search module. In my case, I used the following code.

<?php
/**
 * @file
 * Contains \Drupal\ua_sc_module\Routing\SearchAlterRouteSubscriber.
 */

namespace Drupal\ua_sc_module\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class SearchAlterRouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    // Remove the /search route.
    $collection->remove('search.view');
  }

}

Tags:

Routes

8