24

What is the deal with all the parameters with, and without, a prefixed underscore character?

Where does Drupal decide how to process these parameters?

Was this concept introduced from Symfony, or is it new to Drupal?

Example (node.routing.yml):

node.overview_types:
  path: '/admin/structure/types'
  defaults:
    _controller: '\Drupal\Core\Entity\Controller\EntityListController::listing'
    entity_type: 'node_type'
    _title: 'Content types'
  requirements:
    _permission: 'administer content types'
  • 2
    It's a Symfony convention. There's a good article here, find the bit that says The final thing to pay attention to is the special meaning of the underscore character in the parameter names. Parameters that start with this character have special meaning – Clive Oct 23 '13 at 10:08
  • 1
    Thanks Clive. This article mentions "special meaning", but does not explain this at all. Why cant non-underscore parameters be special too? – Daniel Oct 23 '13 at 11:12
  • 1
    lol, Why cant non-underscore parameters be special too?, that sounds like a deeply existential question! Usually (just usually) prefixing variables is done to either indicate a 'private' var (unlikely here), or to help avoid naming collisions with other classes/methods/something else in a system. Would be good to see the official docs, yes – Clive Oct 23 '13 at 11:17
41
+50

Here comes a hopefully good explanation behind the idea of the routing system as well as the drupal specific additions to it.

General overview

The Symfony components have two important concepts here. The http kernel is a system which gets the request, somehow ask other systems to produce to define the piece of code which produces the requested output (a response object) and send the response back to the client. This piece of code is called controller, so this can be either a pure php4 like function, a method on an object or even a anonymous function.

The system that knows which controller is responsible for the current request is the routing system.

enter image description here

Basic routing file

As module developer you define the list of routes and the corresponding controllers.

Here is an example for a json response:

taxonomy.autocomplete_vid:
  path: '/taxonomy/autocomplete_vid/{taxonomy_vocabulary}'
  defaults:
    _controller: '\Drupal\taxonomy\Controller\TermAutocompleteController::autocompletePerVid'
  requirements:
    taxonomy_vocabulary: \d+

Most symfony documentation mention pattern, but drupal decided to just allow the non-deprecated "path" key in its routing file.

The key concept is the controller which gets some parameters from the system and converts them to the response. In this example you have the parameter 'taxonomy_vocabulary'. So everything without an underscore is considered to be a parameter to the controller. If you want to specify a default value, you put it into the defaults array. In the same yml array you specify the class and method connected with '::' to tell the system where to look up stuff. Every other property has nothing to do with the controller parameters and so are considered to be internal and so have an underscore as prefix.

Symfony itself also allows you to define regular expressions to validate that the incoming parameter is valid (using 'requirements'). Here it would match to only numbers.

Controller resolver

Once symfony found out which controller is active on the current request, it asks the so called controller resolver to create an instance of the controller, that can be executed via call_user_func_array. The controller resolver has one method to get the controller callable (object + method, anonymous function) and one method to get the parameters passed to the controller, see Controller resolver

Drupal extensions

This is basically what symfony gives you.

Drupal though is slightly more complicated:

  • You can check access to the route. For example calling user_access() was very common in Drupal 7 and below.
  • You don't want to convert the taxonomy_vocabulary to its actual entity object
  • You don't want to generate the full page response, but just the "main content".

Access check

Drupal has introduced a system on top of the symfony parts that checks whether the user has access to the current route and alternative throw a 403 (access denied) exception. Access manager

In the routing file you specify this in the requirements part. The most common bits are listed in the example:

  path: '/user/{user}'
  options:
    _access_mode: 'ANY'
  requirements:
    _permission: 'access user profiles'
    _entity_access: 'user.view'
    _role: 'administrator'

_permission defines a call to user_access(), _role ensures that the user has a certain role (you can specify multiple ones via , for OR and + for AND logic). _entity_access asks the entity system whether you have access to view the user entity. Per default drupal ensures that you add access checkers allow you to proceed, but you can switch it in the options via the _access_mode.

Upcasting

As mentioned in the listing you don't want to take care about loading an entity, see /user/{user} as example. For entities you basically just use the name of the entity type and it will execute a entity_load with the ID passed in the URL. Param converter manager

Page response

As written before the controller is responsible to generate the response object. This would be horrible in Drupal as a page consists of so much more like all the blocks appearing in its regions, the html and page templates etc. Therefore drupal specified a different key to specify a controller which returns the content of a page:

user.page:
  path: '/user'
  defaults:
    _content: '\Drupal\user\Controller\UserController::userPage'
  requirements:
    _access: 'TRUE'

The string defined is the controller used to generate the render array for the main content region of your page.

Another addition is also the way how to deal with forms, as returning a page with a form is slightly more complex than just a render array, so you can define _form with the FormInterface responsible for the current form.

user.pass:
  path: '/user/password'
  defaults:
    _form: '\Drupal\user\Form\UserPasswordForm'
  requirements:
    _access: 'TRUE'

Note: This covers the most important points from my perspective, though there are for sure many more points to talk about.

TL;DR

  • Underscores are specified for everything which are not parameters to the controller. This is coming as a sort of "standard" from symfony.
  • These parameters are upcasted via the param converter and passed to the controller using the controller resolver
  • Drupal has some additions to make it easier for people to interact with the symfony routing system.
  • Wow. Impressive answer. Why do certain parameters have periods in them opposed to using an underscore? E.g. user.pass (in the example above) vs. user_pass. Is that a symfony convention as well? – chrisjlee Nov 6 '13 at 1:03
  • 2
    There is some kind of convention to use $module.$name as machine name of a route. Nothing though assumes that internally. – Daniel Wehner Nov 7 '13 at 10:18
  • As per the issue below, _content is not used at all anymore, but _controller is. So the example in the Page Response part is not up to date. drupal.org/node/2378809 If we want to display data in the content region of our page, then the controller will define a render array, similarly to how it's done in Drupal 7. If we want to bypass that, and create our page from scratch, then we can return a Response object. – benelori Apr 10 '15 at 13:41
  • Well sure, you can't expect that 1.5 years don't happen – Daniel Wehner Apr 10 '15 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.