Parameter upcasting in routes

Last updated on
16 June 2017

Drupal 8's routes may include placeholder elements which designate places where the URL contains dynamic values. By naming these placeholders, the system can upcast (convert) those values to actual object instances. For example if a node's base path is '/node/{node}', then {node} is a placeholder. The ParamConverter system takes care of converting that parameter to a node object instance automatically.

Menu parameter upcasting means converting a menu argument to anything which could be used in the route controllers. It can be an object or an array.

Let's take an example of the following code in Drupal 7:
In my_module.module

function my_module_menu() {
  $items['node/%my_menu/mytab'] = array(
    // ...
    // ...
  );
}

The my_module_menu() function implementing hook_menu() shows a menu item with an argument %my_menu. Suppose we want the callback function for this menu item to receive an object after doing some processing on the value passed from the url. e.g., we want to load a specific field of the node with nid 1 when we hit node/1/mytab.

To accomplish the above in Drupal 7, would require us to create a loader function like the one below:

function my_menu_load($arg) {
  // Do whatever with argument and return your values
}

The page callback for the menu would receive whatever is returned from the loader function defined above.

However, when it comes to Drupal 8, we all know the shift is towards configuration in YAML files & Object-oriented structure of the code. Drupal 8 construct to achieve this is making use of ParamConverter interface. To port the example mentioned above, we will need to do the following:

  1. Create my_module.routing.yml
  2. Create my_module.services.yml describing metadata for your custom paramconverter implementing the paramconverter interface
  3. Implement the custom paramconverter in a PHP class namespaced in my_module.services.yml
  4. Implement the callback for your menu item defined in my_module.routing.yml

Porting your menu item into Drupal 8:
In my_module.routing.yml

my_module.mymenu:
  path: '/node/{my_menu}/mytab'
  defaults:
    _title: 'My Title'
    _form: '\Drupal\mymodule\Form\MyModuleformControllerForm'
  options:
    parameters:
      my_menu:
        type: my_menu

This is how a typical route would look like in Drupal 8. The route described above is going to render a form on the page depending on the my_menu argument passed down to it.

It's very important that the name of the parameter matches the variable in the page callback arguments. e.g., if the parameter name is declared as my_menu in routing.yml file, the callback function would receive the upcasted value in the $my_menu variable.

Special case: Entity parameters

If in case of entity type, you do not need to implement ParamConverter class. Simply in routing.yml write type: entity:my_entity_type instead of type: my_menu. Note: entity:entity_type must not be surrounded by quotation marks.

Building a Parameter Converter service

Creating my_module.services.yml:

services:
  my_menu:
    class: Drupal\mymodule\ParamConverter\MyModuleParamConverter
    tags:
      - { name: paramconverter }

Make sure the tag value says paramconverter. This helps Drupal while rebuilding its cache to compile the services accordingly.

Creating MyModuleParamConverter as defined in the namespace above in
src/ParamConverter/MyModuleParamConverter.php

namespace Drupal\mymodule\ParamConverter;
 
use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\node\Entity\Node;
use Symfony\Component\Routing\Route;
 
class MyModuleParamConverter implements ParamConverterInterface {
  public function convert($value, $definition, $name, array $defaults) {
    return Node::load($value);
  }
 
  public function applies($definition, $name, Route $route) {
    return (!empty($definition['type']) && $definition['type'] == 'my_menu');
  }
}

This class implements ParamConverterInterface provided by Drupal 8 core. There are 2 functions implemented above:

  1. public function convert(): All your logic related to processing of the url argument goes in here. In the example above, we are converting the value picked up from the url into a node object.
  2. public function applies(): This is a validation function describing where the param conversion will be applicable. Definition variable receives the definition for the menu parameter as defined in the routing.yml file. Since we want this conversion to be applicable only for parameters of type my_menu, the check is applied accordingly.

And, now at the last, our Callback class for the menu item in src/Form/MyModuleformControllerForm.php:

.
.
.
Class MyModuleformControllerForm extends FormBase{
  .
  .
  .
  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $my_menu = NULL) {
    // $my_menu will be converted object from convert function above.
  }
  .
  .
  .
}

The callback above is focussed only on the buildForm function since that is where the upcasted argument will be received.

NOTE: The variable name must match the parameter value {my_menu}. The data type of the argument will depend on the return value from the convert function in MyModuleParamConverter.php.

These small chunks of code & you are all set to convert your menu parameters into almost anything required by your custom or contrib modules.

Content is taken from : http://www.qed42.com/blog/upcasting-menu-parameters-drupal-8