A Deep Dive into Drupal
8 Routing
DrupalCamp Mumbai
April 1, 2017
Naveen Valecha
About me
● Drupal: naveenvalecha
● Git Administer on Drupal.org
● Site Maintainer of groups.drupal.org
● Twitter: @_naveenvalecha_
● Web: https://www.valechatech.net
● Drupal 5,6,7,8
Agenda
● What are Routes. Why we need them?
● Routes and Controllers
● Access checking on routes
● Custom Access Checkers
● CSRF Prevention on routes
● Altering routes
● Dynamic Routes
● Parameter Upcasting
Routing System
● hook_menu to Symfony2 routing
● Replace paths with route names for rendering
links
● Converting all page callbacks to controllers
● New breadcrumb system, new menu link system,
conversion of local tasks and actions to plugins
Routing System - Cont.
● menu links, local tasks, local actions, contextual
links
● Split all the pieces from hook_menu into YAML
files finally
● module.routing.yml module.links.menu.yml
● module.links.task.yml module.links.action.yml
● module.contextual.yml
hook_menu to Symfony Routing
● PHP array to multiple yaml files
● Performance improvements
● Developer Experience(DX)
● Clean Code
● Procedural to Object Oriented(OO)
D7 hook_menu
$items['admin/appearance/settings'] = array(
'title' => 'Settings',
'description' => 'Configure default and theme specific settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('system_theme_settings'),
'access arguments' => array('administer themes'),
'type' => MENU_LOCAL_TASK,
'file' => 'system.admin.inc',
'weight' => 20,
);
D8 system.routing.yml
system.theme_settings:
path: '/admin/appearance/settings'
defaults:
_form: 'DrupalsystemFormThemeSettingsForm'
_title: 'Appearance settings'
requirements:
_permission: 'administer themes'
D8 system.links.task.yml
system.theme_settings:
route_name: system.theme_settings
title: 'Settings'
base_route: system.themes_page
weight: 100
Route
● A route is a path which is defined for Drupal to
return some sort of content on. For example, the
default front page, '/node' is a route.
● Use mm.routing.yml for defining routes
Routes and Controllers
● The routing system is responsible for matching
paths to controllers, and you define those
relations in routes. You can pass on additional
information to your controllers in the route.
Access checking is integrated as well.
Route - Slug
entity.node.preview:
path: '/node/preview/{node_preview}/{view_mode_id}'
defaults:
_controller: 'DrupalnodeControllerNodePreviewController::view'
_title_callback: 'DrupalnodeControllerNodePreviewController::title'
requirements:
_node_preview_access: '{node_preview}'
options:
parameters:
node_preview:
type: 'node_preview'
/**
* Defines a controller to render a single node in preview.
*/
class NodePreviewController extends EntityViewController {
/**
* {@inheritdoc}
*/
public function view(EntityInterface $node_preview, $view_mode_id =
'full', $langcode = NULL) {
$node_preview->preview_view_mode = $view_mode_id;
$build = parent::view($node_preview, $view_mode_id);
return $build;
}
}
example.content:
path: '/example'
defaults:
_controller:
'DrupalexampleControllerExampleController::content'
custom_arg: 12
requirements:
_permission: 'access content'
// ...
public function content(Request $request, $custom_arg) {
// Now can use $custom_arg (which will get 12 here) and $request.
}
Routes Structure
● Path(*): /node/preview/{node_preview}/{view_mode_id}
● defaults(*)
○ _controller:
DrupalnodeControllerNodePreviewController::view
○ _form: DrupalCoreFormFormInterface
○ _entity_view, _entity_form:
○ _title(optional), _title_context(optional),
_title_callback(optional)
Routes Structure
● methods(optional)
● Requirements
○ _permission, _role, _access, _entity_access,
_custom_access, _format,
_content_type_format
○ _module_dependencies
○ _csrf_token
Access checking
Permission
requirements:
_permission: 'administer content types'
Role
requirements:
_role: 'administrator'
Access checking - Custom
class NodeRevisionAccessCheck implements AccessInterface {
public function access(Route $route, AccountInterface $account, $node_revision =
NULL, NodeInterface $node = NULL) {
if ($node_revision) {
$node = $this->nodeStorage->loadRevision($node_revision);
}
$operation = $route->getRequirement('_access_node_revision');
return AccessResult::allowedIf($node && $this->checkAccess($node, $account,
$operation))->cachePerPermissions()->addCacheableDependency($node);
}
}
Route - CSRF Protection
aggregator.feed_refresh:
path: '/admin/config/services/aggregator/update/{aggregator_feed}'
defaults:
_controller:
'DrupalaggregatorControllerAggregatorController::feedRefresh'
_title: 'Update items'
requirements:
_permission: 'administer news feeds'
_csrf_token: 'TRUE'
Routes - Altering
class NodeAdminRouteSubscriber extends RouteSubscriberBase {
protected function alterRoutes(RouteCollection $collection) {
if ($this->configFactory->get('node.settings')->get('use_admin_theme')) {
foreach ($collection->all() as $route) {
if ($route->hasOption('_node_operation_route')) {
$route->setOption('_admin_route', TRUE);
}
}
}
}
}
Dynamic Routes
Image.routing.yml
route_callbacks:
- 'DrupalimageRoutingImageStyleRoutes::routes'
Dynamic Routes - Cont.
class ImageStyleRoutes implements ContainerInjectionInterface {
public function routes() {
$routes = [];
$directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
$routes['image.style_public'] = new Route(
'/' . $directory_path . '/styles/{image_style}/{scheme}',
[ '_controller' => 'DrupalimageControllerImageStyleDownloadController::deliver', ],
[ '_access' => 'TRUE', ]
);
return $routes;
}
}
Route-Parameter Upcasting
entity.node.preview:
path: '/node/preview/{node_preview}/{view_mode_id}'
defaults:
_controller: 'DrupalnodeControllerNodePreviewController::view'
_title_callback: 'DrupalnodeControllerNodePreviewController::title'
requirements:
_node_preview_access: '{node_preview}'
options:
parameters:
node_preview:
type: 'node_preview'
Route-Parameter Upcasting Cont.
node.services.yml
node_preview:
class: DrupalnodeParamConverterNodePreviewConverter
arguments: ['@user.private_tempstore']
tags:
- { name: paramconverter }
lazy: true
Route-Parameter Upcasting Cont.
class NodePreviewConverter implements ParamConverterInterface {
public function convert($value, $definition, $name, array $defaults) {
$store = $this->tempStoreFactory->get('node_preview');
if ($form_state = $store->get($value)) {
return $form_state->getFormObject()->getEntity();
}
}
public function applies($definition, $name, Route $route) {
if (!empty($definition['type']) && $definition['type'] == 'node_preview') {
return TRUE;
}
return FALSE;
}
What’s for Drupal 9?
● Consider having a single class for Match Dumper,
Route Provider and Route Builder
● Automatically unserialize request data and
serialize outgoing data
References
● http://symfony.com/doc/current/components/
routing.html
● https://www.drupal.org/docs/8/api/routing-syste
m
Questions?
THANK YOU!

A deep dive into Drupal 8 routing

  • 1.
    A Deep Diveinto Drupal 8 Routing DrupalCamp Mumbai April 1, 2017 Naveen Valecha
  • 2.
    About me ● Drupal:naveenvalecha ● Git Administer on Drupal.org ● Site Maintainer of groups.drupal.org ● Twitter: @_naveenvalecha_ ● Web: https://www.valechatech.net ● Drupal 5,6,7,8
  • 3.
    Agenda ● What areRoutes. Why we need them? ● Routes and Controllers ● Access checking on routes ● Custom Access Checkers ● CSRF Prevention on routes ● Altering routes ● Dynamic Routes ● Parameter Upcasting
  • 4.
    Routing System ● hook_menuto Symfony2 routing ● Replace paths with route names for rendering links ● Converting all page callbacks to controllers ● New breadcrumb system, new menu link system, conversion of local tasks and actions to plugins
  • 5.
    Routing System -Cont. ● menu links, local tasks, local actions, contextual links ● Split all the pieces from hook_menu into YAML files finally ● module.routing.yml module.links.menu.yml ● module.links.task.yml module.links.action.yml ● module.contextual.yml
  • 6.
    hook_menu to SymfonyRouting ● PHP array to multiple yaml files ● Performance improvements ● Developer Experience(DX) ● Clean Code ● Procedural to Object Oriented(OO)
  • 7.
    D7 hook_menu $items['admin/appearance/settings'] =array( 'title' => 'Settings', 'description' => 'Configure default and theme specific settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('system_theme_settings'), 'access arguments' => array('administer themes'), 'type' => MENU_LOCAL_TASK, 'file' => 'system.admin.inc', 'weight' => 20, );
  • 8.
    D8 system.routing.yml system.theme_settings: path: '/admin/appearance/settings' defaults: _form:'DrupalsystemFormThemeSettingsForm' _title: 'Appearance settings' requirements: _permission: 'administer themes'
  • 9.
  • 10.
    Route ● A routeis a path which is defined for Drupal to return some sort of content on. For example, the default front page, '/node' is a route. ● Use mm.routing.yml for defining routes
  • 11.
    Routes and Controllers ●The routing system is responsible for matching paths to controllers, and you define those relations in routes. You can pass on additional information to your controllers in the route. Access checking is integrated as well.
  • 12.
    Route - Slug entity.node.preview: path:'/node/preview/{node_preview}/{view_mode_id}' defaults: _controller: 'DrupalnodeControllerNodePreviewController::view' _title_callback: 'DrupalnodeControllerNodePreviewController::title' requirements: _node_preview_access: '{node_preview}' options: parameters: node_preview: type: 'node_preview'
  • 13.
    /** * Defines acontroller to render a single node in preview. */ class NodePreviewController extends EntityViewController { /** * {@inheritdoc} */ public function view(EntityInterface $node_preview, $view_mode_id = 'full', $langcode = NULL) { $node_preview->preview_view_mode = $view_mode_id; $build = parent::view($node_preview, $view_mode_id); return $build; } }
  • 14.
    example.content: path: '/example' defaults: _controller: 'DrupalexampleControllerExampleController::content' custom_arg: 12 requirements: _permission:'access content' // ... public function content(Request $request, $custom_arg) { // Now can use $custom_arg (which will get 12 here) and $request. }
  • 15.
    Routes Structure ● Path(*):/node/preview/{node_preview}/{view_mode_id} ● defaults(*) ○ _controller: DrupalnodeControllerNodePreviewController::view ○ _form: DrupalCoreFormFormInterface ○ _entity_view, _entity_form: ○ _title(optional), _title_context(optional), _title_callback(optional)
  • 16.
    Routes Structure ● methods(optional) ●Requirements ○ _permission, _role, _access, _entity_access, _custom_access, _format, _content_type_format ○ _module_dependencies ○ _csrf_token
  • 17.
    Access checking Permission requirements: _permission: 'administercontent types' Role requirements: _role: 'administrator'
  • 18.
    Access checking -Custom class NodeRevisionAccessCheck implements AccessInterface { public function access(Route $route, AccountInterface $account, $node_revision = NULL, NodeInterface $node = NULL) { if ($node_revision) { $node = $this->nodeStorage->loadRevision($node_revision); } $operation = $route->getRequirement('_access_node_revision'); return AccessResult::allowedIf($node && $this->checkAccess($node, $account, $operation))->cachePerPermissions()->addCacheableDependency($node); } }
  • 19.
    Route - CSRFProtection aggregator.feed_refresh: path: '/admin/config/services/aggregator/update/{aggregator_feed}' defaults: _controller: 'DrupalaggregatorControllerAggregatorController::feedRefresh' _title: 'Update items' requirements: _permission: 'administer news feeds' _csrf_token: 'TRUE'
  • 20.
    Routes - Altering classNodeAdminRouteSubscriber extends RouteSubscriberBase { protected function alterRoutes(RouteCollection $collection) { if ($this->configFactory->get('node.settings')->get('use_admin_theme')) { foreach ($collection->all() as $route) { if ($route->hasOption('_node_operation_route')) { $route->setOption('_admin_route', TRUE); } } } } }
  • 21.
  • 22.
    Dynamic Routes -Cont. class ImageStyleRoutes implements ContainerInjectionInterface { public function routes() { $routes = []; $directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath(); $routes['image.style_public'] = new Route( '/' . $directory_path . '/styles/{image_style}/{scheme}', [ '_controller' => 'DrupalimageControllerImageStyleDownloadController::deliver', ], [ '_access' => 'TRUE', ] ); return $routes; } }
  • 23.
    Route-Parameter Upcasting entity.node.preview: path: '/node/preview/{node_preview}/{view_mode_id}' defaults: _controller:'DrupalnodeControllerNodePreviewController::view' _title_callback: 'DrupalnodeControllerNodePreviewController::title' requirements: _node_preview_access: '{node_preview}' options: parameters: node_preview: type: 'node_preview'
  • 24.
    Route-Parameter Upcasting Cont. node.services.yml node_preview: class:DrupalnodeParamConverterNodePreviewConverter arguments: ['@user.private_tempstore'] tags: - { name: paramconverter } lazy: true
  • 25.
    Route-Parameter Upcasting Cont. classNodePreviewConverter implements ParamConverterInterface { public function convert($value, $definition, $name, array $defaults) { $store = $this->tempStoreFactory->get('node_preview'); if ($form_state = $store->get($value)) { return $form_state->getFormObject()->getEntity(); } } public function applies($definition, $name, Route $route) { if (!empty($definition['type']) && $definition['type'] == 'node_preview') { return TRUE; } return FALSE; }
  • 26.
    What’s for Drupal9? ● Consider having a single class for Match Dumper, Route Provider and Route Builder ● Automatically unserialize request data and serialize outgoing data
  • 27.
  • 28.
  • 29.