Using parameters in routes

Last updated on
17 November 2024

In order to pass dynamic values from the URI to the controller you can use parameters. Parameters are defined in the route path using curly braces. For example if the path is defined as '/node/{node}/edit', then a request to the /node/1234/edit URI would make the node object with ID 1234 available in the controller as the $node argument.

Example

A common use case for parameters is to refer to a content or configuration entity type in the route path. For example in example.routing.yml:

example.user:
  path: '/example/{user}'
  defaults: 
    _controller: '\Drupal\example\Controller\ExampleController::content' 
  requirements: 
    _permission: 'access content' 
  options:
    parameters:
      user:
        type: entity:user

The {user} element in the URL is called a slug (also called a "path parameter" or sometimes a "named parameter") and is available as $user in the controller method. While Symfony allows for more arbitrary use of slugs, Drupal has stricter requirements. In fact, unlike generic Symfony routes, Drupal requires that a slug occupies a complete path part - the portion between two slashes (or everything after the last slash). If you must pass a parameter containing slashes, apply the same trick as in PathProcessorFiles.

In most PHP code the name of the variable does not matter but here it does: the name of the method argument must match the slug. Conversely, if the method argument name matches the name of the slug, the parameter will be passed in, irrespective of the order of arguments.

When your parameter should be an instance of an entity, you have to define your parameter type inside the options parameters section (type: entity:user in the example above) in order to let Drupal check and validate the provided entity id for you. This will ensure that a "not found" response is provided if the provided entity ID doesn't exist. Take a look on video explaining this feature here

There is nothing more to provide in the route for the parameter conversion to happen because user is the name of an entity type and it will get automatically converted into a user object. In the controller, define an argument on the controller method with the same name to get this user object passed in:

use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;

class ExampleController {  
  
  // ...
  public function content(AccountInterface $user, Request $request) {
    // Do something with $user.
  }
}

This works because

  1. $user is an instance of Drupal\user\Entity\User and that object implements AccountInterface. Type hinting with Drupal\user\UserInterface or even Drupal\Core\Entity\EntityInterface or any other interface the User class implements would make the argument passing work as well. (Typehinting with classes works as well up to the User class itself but it's best practice to typehint with interfaces.)
  2. When typehinting with Request the request object is automatically passed in (even the argument name does not matter). See below for other objects that can be set as parameters.

Note: the order of the method arguments is not important. Only the name for slugs and the typehint for typehinted parameters (see below). However optional arguments should always be placed last.

In this case, if the URL includes the user id of a non-existent user, this will return a 404 Not Found response automatically.

Forms can use upcasted URL parameters too. In this example a form is using the account object and its ID is passed in from the URL.

example.user_form:
  path: '/example/form/{user}'
  defaults:
    _form: '\Drupal\example\Form\ExampleForm'
  requirements:
    _permission: 'access content'
namespace Drupal\example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Session\AccountInterface;

class ExampleForm extends FormBase {

 public function buildForm(array $form, FormStateInterface $form_state, AccountInterface $user = NULL) {
    // Do something with $user in the form
  }
}

Note that this method of providing the entity data to a form is not used for entity forms, such as forms to add/edit/delete entity data.

Note: For form arguments, it is essential to provide a default value for each argument!
In the example above, $user is given a default value of NULL. Without this, your code will not compile.

Typehinted parameters

The following special objects can be passed into a route's controller method if there is a parameter with the correct typehint. The name of the parameter is not important.

  • \Symfony\Component\HttpFoundation\Request: The raw Symfony request object. It is generally only useful if the controller needs access to the query parameters of the request. Note a few Request quirks: the $_GET superglobal can be accessed in $request->query (and it's a ParameterBag, not an array) while the session information can be accessed via $request->getSession() which returns a session object with its own methods.
  • \Drupal\Core\Routing\RouteMatchInterface: The "route match" data from this request. This object contains various standard data derived from the request and routing process. To inspect the actual route, for example to check for parameters, or default values, or options, use $route_match->getRouteObject().
  • \Psr\Http\Message\ServerRequestInterface: The raw request, represented using the PSR-7 ServerRequest format.

Security on routes

Multiple parameters on a single route

If two entities of the same type are required in the same route then the typehinting magic above won't work and the slug-to-argument name magic as explained in the parameters in routes handbook page needs to be used. However, upcasting still works, just the system needs a little help:

route_with_two_nodes:
  path: '/foo/{node1}/{node2}'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::foo'
  options:
    parameters:
      node1:
        type: entity:node
      node2:
        type: entity:node
use Drupal\node\NodeInterface;

class ExampleController {
  function foo(NodeInterface $node1, NodeInterface $node2) {
  }
}

Restrict entity route parameters by specified bundles

Entity route parameters are accepting a new bundle definition property of type array. By defining such a property, the param conversion will be limited to the specified bundles:

example.route:
  path: foo/{example}
  options:
    parameters:
      example:
        type: entity:node
        bundle:
          - article
          - news

With the above example, the {example} parameter will be converted to a node only if the value is referring to node of type 'article' or 'news'.

Optional parameters

Parameters on routes can be omitted when a default value for the parameter is supplied. Imagine you have a form controller that allows people to report different issues (e.g. bug reports, feature requests and support requests), and if the type is omitted it should default to 'support request'. Supply the default value for the optional parameter in the "defaults" section:

issue.report_form:
  path: '/report/{issue_type}'
  defaults: 
    _controller: '\Drupal\issue\Controller\IssueController::report'
    issue_type: 'support-request'
  requirements: 
    _permission: 'report issue' 

Now if we do a request to '/report' the $issue_type parameter will default to 'support-request'. We can override the value by supplying it on the URL, like '/report/bug'.

The default values for arguments can also be used to provide routes with fixed paths to controllers that expect arguments. Imagine for example that our SEO expert finds it extremely important that our form to submit bug reports is available on the path 'report-a-bug'. We can reuse the same controller as in the previous example, and provide a different default for "issue_type". The router knows that this parameter exists and will pass it on to the controller:

issue.report_a_bug:
  path: '/report-a-bug'
  defaults: 
    _controller: '\Drupal\issue\Controller\IssueController::report'
    issue_type: 'bug'
  requirements: 
    _permission: 'report issue' 

Help improve this page

Page status: No known problems

You can: