Upcasting menu parameters in Drupal 8

SUMIT MADAN   |  24th June, 2015
Menu upcasting means converting a menu argument to anything. It can be an object or an array. In this article, we will look at how it used to be done in Drupal 7 codebase & how should we port this into Drupal 8 codebase.
 
Lets take an example of the following code in Drupal 7:
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 recieve an object after doing some processing on the value passed from the url. e.g., we want to load a specific field of node with nid 1 when we hit node/1/mytab. 

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

my_module.module

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

The page callback for the menu would recieve 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 codebase:


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. 

NOTE: If in case of entity type, you do not need to implement paramconverter class. Simply in routing.yml write type: entity:entity_type instead of type: my_menu.

NOTE: Its 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 $my_menu variable.

Creating my_module.services.yml:

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:

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 convertion will be applicable. Definition variable recieves the definition for the menu parameter as defined in the routing.yml file. Since we want this convertion 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:

src/Controller/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 recieved.

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.

Looking for a Drupal partner ?

We are drupal 8 ready