20

In other words, what is the Drupal 8 equivalent of hook_menu_alter()?

Drupal 8 still uses hook_menu(), but for what I can see, the information returned by the hook is different from what the hook returned in Drupal 7. For example, the definition given in user_menu() for user is the following.

  $items['user'] = array(
    'title' => 'User account',
    'title callback' => 'user_menu_title',
    'weight' => -10,
    'route_name' => 'user_page',
    'menu_name' => 'account',
  );

The route_name property links to an entry in the user.routing.yml file.

user_page:
  pattern: '/user'
  defaults:
    _content: '\Drupal\user\Controller\UserController::userPage'
  requirements:
    _access: 'TRUE'

This is different from what done with Symphony, and it confuses me about how a module can alter the route defined from another user.

The only function that is still invoking hook_menu_alter() is menu_router_build(), but that function still contains code that needs to be updated, since it is still using the now deprecated drupal_alter().

  // Alter the menu as defined in modules, keys are like user/%user.
  drupal_alter('menu', $callbacks);
  foreach ($callbacks as $path => $router_item) {
    // If the menu item is a default local task and incorrectly references a
    // route, remove it.
    // @todo This may be removed later depending on the outcome of
    // http://drupal.org/node/1889790
    if (isset($router_item['type']) && $router_item['type'] == MENU_DEFAULT_LOCAL_TASK) {
      unset($callbacks[$path]['route_name']);
    }
    // If the menu item references a route, normalize the route information
    // into the old structure. Note that routes are keyed by name, not path,
    // so the path of the route takes precedence.
    if (isset($router_item['route_name'])) {
      $router_item['page callback'] = 'USES_ROUTE';
      $router_item['access callback'] = TRUE;
      $new_path = _menu_router_translate_route($router_item['route_name']);

      unset($callbacks[$path]);
      $callbacks[$new_path] = $router_item;
    }
  }
0

3 Answers 3

13

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.

2
15

Since I asked the question, Drupal 8 core code has been changed. Some of the issues about routes have been fixed.

hook_menu() isn't used from Drupal 8 anymore; the routes a module uses are defined in a .routing.yml file (for example user.routing.yml). Alter hooks are still used, but since hook_menu() isn't invoked from Drupal core, hook_menu_alter() won't be invoked.

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 a route controller changed from _content to _controller
0
10

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.
    if ($collection->get('search.view')) {
      $collection->remove('search.view');
    }
  }

}

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.