Last updated September 20, 2015. Created on October 21, 2013.
Edited by HongPong, pfrenssen, sidharrell, jmolivas. Log in to edit this page.

This is a quick introduction to routes and controllers in Drupal 8. If you're only modifying or extending existing functionality, you may not need to know about routes. However, if you want to expose content or functionality on your own URIs on a site, routing is an important part of writing your module. This can help provide functionality at specific URIs of a website or just modify or augment existing functionality.

Routing in routing.yml files

When you named your module's .info.yml file, it was of the form module_name.info.yml. That module_name is registered with core Drupal as the "Machine name" of your module. You can open up the details of your module in the module list to see it. Drupal then loads the routing file that is of the form module_name.routing.yml that it will use to define how Drupal will behave when a specific path is encountered. For example, if we had a file example.info.yml that had defined our module, we would then have a file example.routing.yml:

example.content:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
    _title: 'Hello World'
  requirements:
    _permission: 'access content'

This tells Drupal 8 that a route named 'example.content' (named with the module name as prefix) exists and is bound to the URI '/example' on the site. When that path is accessed, the 'access content' permission is checked on the accessing user and, if access is granted, the ExampleController::content method is invoked and a page is displayed with the title 'Hello World'.

Note that Drupal's autoloading mechanism loaded the file at modules/example/src/Controller/ExampleController.php because when it encountered the machine name "example" in the string _controller, it looks in the src folder of that module. If your files are not autoloading, you may want to recheck the registered machine name of your module.

If you built Drupal 7 modules before, this is pretty similar to how Drupal 7 associated page callbacks to paths in hook_menu(). (However, the routing system is not responsible for managing tabs, action links and contextual links).

Building a page controller

Drupal 8 uses the Symfony HTTP Kernel, a system which gets the request and asks other systems to produce the requested output (a response object) and sends the response back to the client. The output is generated by a piece of code called the controller. In theory the controller can be either a pure PHP 4 function, a method on an object or even an anonymous function. See more about the Routing system

The second part of creating a page that outputs our content is to put the page controller in place. This could be a PHP 4 style function, but best practice in Drupal 8 is to use a controller class. This class is in a file named exactly after the controller provided in the routing file, ExampleController.php

For the above router the class needs to be placed in example/src/Controller directory and with name ExampleController.php. So the full path will look like, example/src/Controller/ExampleController.php.

<?php
/**
 * @file
 * Contains \Drupal\example\Controller\ExampleController.
 */

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;

class ExampleController extends ControllerBase {

  /**
   * {@inheritdoc}
   */
  public function content() {
    $build = array(
      '#type' => 'markup',
      '#markup' => t('Hello World!'),
    );
    return $build;
  }

}
?>

With only the routing file and the page controller, we made a page available on our site on /example that outputs 'Hello World!' on a page titled 'Hello World'.

See also: ControllerBase abstract class API docs.

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

okbel’s picture

my_module.content:
path: '/admin/my_module/{method}'
defaults:
_controller: '\Drupal\my_module\Controller\MyModuleController::%{method}'

HI!
How can I send a a parameter to the _controller? to switch between the methods of the controller

Thank you!

artfulrobot’s picture

And I'd guess as such you can't do it that way, but if you wanted to save lines in the routing file, that pattern would be easy to implement in a method like

In your YAML file

_controller: '\Drupal\my_module\Controller\MyModuleController::mySubRouter'

In your controller class...

<?php
// ...
public function mySubRouter($method) {
   // @todo validate user-input somehow - what if there are
   // methods users should not be able to call?
   return $this->$method();
}
?>

Rich

jaxxed’s picture

The single handler is a good idea, especially as it keeps things simple. You should probably perform some validation on the method handler, and ideally isolate to only certain values (switch or something,)

another option is to use a RouteProvider, which is a class that can be used to register multiple routes, for example based on data and content, if your route options are not static.

m4olivei’s picture

How do you inject a service into a controller using dependancy injection?

D4K0’s picture

Well, you may need to implement Symfony ContainerInterface for DI.

Let's assume we need to format current date according to Drupal regional and language settings.
We can achieve this by injecting date.formatter core service to perform date formating.

First, notice the line use Symfony\Component\DependencyInjection\ContainerInterface;.
We need this line as a requirement of the create() method. This method creates a new instance of our controller class passing to it our service retrieved from the container.

Then, in the controller's constructor, an instance of the service class gets stored in the $datetime property.
The injected service is now accessible via this property and we can use it to call its methods.

Check out this little snippet.

<?php

/**
* @file
* Contains Drupal\demo\Controller\DefaultController.
*/

namespace Drupal\demo\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Datetime\DateFormatter;

/**
* Class DefaultController.
*
* @package Drupal\demo\Controller
*/
class DefaultController extends ControllerBase {

  /**
   * Drupal\Core\Datetime\DateFormatter definition.
   *
   * @var Drupal\Core\Datetime\DateFormatter
   */
  protected $datetime;

  /**
   * {@inheritdoc}
   */
  public function __construct(DateFormatter $datetime) {
    $this->datetime = $datetime;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('date.formatter')
    );
  }

  /**
   * Index.
   *
   * @return array
   *   Render array with current date formatted
   */
  public function index() {
    $now = time();
    $dateFormatted = $this->datetime->format($now);

    return [
        '#type' => 'markup',
        '#markup' => $this->t("Date formatted: ") . $dateFormatted
    ];
  }

}

Hope this helps.

estoyausente’s picture

I had some problems building the controller path.

example.content:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
    _title: 'Hello World'
  requirements:
    _permission: 'access content

The correct is

namespace\ClassName\MethodName

(Is exactly like the example, but I had problem with it and I prefer comment it)

Drupal como paradigma.

mkudenko’s picture

Is it possible to specify a twig template, so that I don't have to use #markup for page content?