Routing

with Drupal 8


Built with http://slid.es/ | reveal.js

Topics

  • Routes
  • Upcasting
  • Access
  • Menu links
  • Local actions
  • Local tasks
  • Titles
  • Entity
  • Breadcrumbs

Routes

  • yourmodule.routing.yml
  • Path to controller/form/... mapping
  • Upcasting (node/5 => $node)
  • Acess rules

Links


http://previousnext.com.au/blog/using-drupal-8s-new-route-controllers 

http://previousnext.com.au/blog/controlling-access-drupal-8-routes-access-checks

http://effulgentsia.drupalgardens.com/content/drupal-8-hello-oop-hello-world

https://drupal.org/node/1800686

Basic route

7.x
function trousers_menu() {
  $items['trousers'] = array(
    'title' => 'Trousers',
    'page callback' => 'trousers_page',
    'access arguments' => 'view trousers',
    'type' => MENU_CALLBACK,
   ); 
}
8.x

trousers_list:
  pattern: '/trousers'
  defaults:
    _content: '\Drupal\trousers\Controller\TrouserController::list'
  requirements:
    _permission: 'view trousers'
    

Controller

7.x
<?phpfunction trousers_page() {

  return t('List of trousers');

}
8.x
class TrouserController extends ControllerBase {

  public function list(Request $request) {

    return $this->t('List of trousers');

  }

} 

Forms

Forms are classes
Extend from FormBase

Relevant methods

getFormId()
buildForm()
validateForm()
submitForm()

Example


class UserPasswordForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'user_pass';
  }
public function buildForm(array $form, array &$form_state) { $form['name'] = $this->t('Use $this->t()'); return $form; } public function validateForm(array &$form, array &$form_state) { // Validate stuff. } public function submitForm(array &$form, array &$form_state) { // Save stuff. } }

Form routes

Use _form instead of _content and a class instead of a method
 system_site_information_settings:
  pattern: '/admin/config/system/site-information'
  defaults:
    _form: 'Drupal\system\Form\SiteInformationForm'
  requirements:
    _permission: 'administer site configuration'

Upcasting / Param Converter

Converts {param} into something
Example: node/5 => $node
Works for entities out of the box
paramconverter_test_node_set_parent:
  pattern: '/paramconverter_test/node/{node}/set/parent/{parent}'
  requirements:
    _access: 'TRUE'
  defaults:
    _content: '\Drupal\paramconverter_test\TestControllers::testNodeSetParent'
  options:
    parameters:
      parent:
        type: 'entity:node'

Custom Converter

Define a service, tag with paramconverter
  paramconverter.awesome:
    class: Drupal\mymodule\AwesomeConverter
    tags:
      - { name: paramconverter } 
 class EntityConverter implements ParamConverterInterface {

  public function convert($value, $definition, $name, array $defaults, Request $request) {    // Convert $value and return it.
  }

  public function applies($definition, $name, Route $route) {
    // Return TRUE if you want to upcast something for that route.
  }


Access Checking

Routes define requirements
Multiple access checks can run on a single route
Mode: All or Any
Matching access checkers are asked for access
Re-usable access checkers

Access definitions

edit_entity_save:
  pattern: '/edit/entity/{entity_type}/{entity}'
  defaults:
    _controller: '\Drupal\edit\EditController::entitySave'
  options:
    _access_mode: 'ALL'
  requirements:
    _permission: 'access in-place editing'
    _access_edit_entity: 'TRUE' 

Re-usable requirements

Check if a user has a permission
_permission: 'my permission name'

Check if a user has a role

_role: 'role1+role2' (AND)
_role: 'role1, role2' (OR)

Check if user is logged in

_user_is_logged_in: 'TRUE'
Allow everyone
_access: 'TRUE' 

We'll get to entities...

Create an access checker

Create a service, tag with access_check
  access_check.aggregator.categories:
    class: Drupal\aggregator\Access\CategoriesAccessCheck
    arguments: ['@database']
    tags:
      - { name: access_check } 
Implement the class
 class CategoriesAccessCheck implements StaticAccessCheckInterface {

  public function appliesTo() {
    return array('_access_aggregator_categories');
  }

  public function access(Route $route, Request $request) {
    // Implement access logic here
  }

Note: There might be an API change that adds AccountInterface $account as third argument to access()

Menu links

New hook_default_menu_links()

Simple structure: link path, title, description, parent

Until that issue is committed: keep hook_menu()

Examples


function node_default_menu_links() {
  $links['admin.content'] = array(
    'link_title' => 'Content',
    'link_path' => 'admin/content',
    'parent' => 'admin',
    'description' => 'Find and manage content.',
  );  $links['admin.structure.types'] = array(
    'link_title' => 'Content types',
    'parent' => 'admin.structure',
    'description' => 'Manage content types,...',
    'route_name' => 'node_overview_types',
  );
  $links['node.add'] = array(
    'link_title' => 'Add content',
    'link_path' => 'node/add',
  );
  return $links;
} 

Local actions

Defined in mymodule.local_actions.yml
Using the plugin system
Route-based
Dynamic local actions not yet solved
But plugin derivatives can be used to derive local actions from something else

Examples

custom_block_type_add:
  route_name: custom_block_type_add
  title: 'Add custom block type'
  appears_on:
    - custom_block_type_list

custom_block_add_action:
  route_name: custom_block_add_page
  title: 'Add custom block'
  appears_on:
    - block_admin_display 

Local tasks

Will work the same way as local actions
Not yet committed: http://drupal.org/node/2050919

Examples

views_ui_settings_tab:
  route_name: views_ui.settings.basic
  title: Settings
  tab_root_id: views_ui_list_tab

views_ui_settings_basic_tab:
  route_name: views_ui.settings.basic
  title: Basic
  tab_root_id: views_ui_list_tab
  tab_parent_id: views_ui_settings_tab

views_ui_settings_advanced_tab:
  route_name: views_ui.settings.advanced
  title: Advanced
  tab_root_id: views_ui_list_tab
  tab_parent_id: views_ui_settings_tab
  weight: 10

Title

drupal_set_title() is gone*

Replacements

_title in route definition
_title_callback in route definition
(TBI in https://drupal.org/node/2076085)
#title in render arrays


*soon

Examples

block_admin_add:
  pattern: '/admin/structure/block/add/{plugin_id}/{theme}'
  defaults:
    _content: '\Drupal\block\Controller\BlockAddController::blockAddConfigureForm'
    _title: 'Configure block'
  requirements:
    _permission: 'administer blocks' 
class Test {

  /**
   * Renders a page with a title.
   */
  public function renderTitle() {
    $build = array();
    $build['#markup'] = 'Hello Drupal';
    $build['#title'] = 'Foo: ' . \Drupal::config()->get('system.site')->get('name');
    return $build;
  }}

Entity & Routing

Closely integrated with viewing, editing and listing entities
Default access checks

Access checkers

View/Update/Delete access
taxonomy_term_edit:
  pattern: '/taxonomy/term/{taxonomy_term}/edit'
  defaults:
    _entity_form: 'taxonomy_term.default'
  requirements:
    _entity_access: 'taxonomy_term.update' 
Create access
taxonomy_term_add:
  pattern: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add'
  defaults:
    _content: '\Drupal\taxonomy\Controller\TaxonomyController::addForm'
  requirements:
    _entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}' 
Needs https://drupal.org/node/2068287

Listing (config) entities

Define a list controller and then use _entity_list
*     "list" = "Drupal\tmgmt\Entity\Controller\TranslatorListController",  
tmgmt_translator_list:
  pattern: '/admin/config/regional/tmgmt_translator'
  defaults:
    _entity_list: 'tmgmt_translator'
  requirements:
    _permission: 'administer tmgmt' 

View an entity

entity_test_render_no_view_mode:
  pattern: '/entity-test-render-no-view-mode/{entity_test_render}'
  defaults:
    _entity_view: 'entity_test_render'

entity_test_render:   pattern: '/entity-test-render/{entity_test_render}'   defaults:     _entity_view: 'entity_test_render.full'

*   controllers = {
 *     "storage" = "Drupal\entity_test\EntityTestStorageController",
 *     "render" = "Drupal\entity_test\EntityTestRenderController"
 *   }, 

Adding / Editing an entity

tmgmt_translator_add:
  pattern: '/admin/config/regional/tmgmt_translator/add'
  defaults:
    _entity_form: tmgmt_translator.add
tmmgt_translator_entity:
  pattern: '/admin/config/regional/tmgmt_translator/manage/{tmgmt_translator}'
  defaults:
    _entity_form: tmgmt_translator.edit

Breadcrumbs 

Sorry, I haven't looked at this yet :)

drupal_set_breadcrumb() replaced with a 
service-based architecture

Work ongoing to have a menu link hierarchy based breadcrumb

Stepping through

Let's take a look at what happens...



Drupal 8 Routing

By Sascha Grossenbacher