Controllers are the best way to create a custom page through code in Drupal 8.

In this article we’ll guide you through creating your first simple controller. We’ll create an example controller that displays a random picture of a dog, through the Dog API.

Creating a module

As with most Drupal functionalities, this one needs to be hosted inside a custom module. Let’s create a simple one.

Protip: If you have Drupal Console installed, you just have to run drupal generate:module and it will ask you the necessary information and create the boilerplate code for you.

We’ll call the module “random_doggo”. Inside modules/custom, we created:

  ● random_doggo/random_doggo.info.yml:

  name: 'random_doggo'
type: module
description: 'Provides an example Controller that gets a random

image of a dog'

core: 8.x
package: 'Custom'

Creating your controller

Your controller will need to be inside src/Controller on your module folder. So we’ll create it there.

Protip: Much like before, if you have Drupal Console, you can also run drupal generate:controller to guide you through creating your Controller boilerplate code. This includes handling the services dependency injection boilerplate, as well as the routing.

So we get random_doggo/src/Controller/RandomDoggoController.php:

  <?php
namespace Drupal\random_doggo\Controller;


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


/**
 * Class RandomDoggoController.
 */

class RandomDoggoController extends ControllerBase {


  /**
   * GuzzleHttp\ClientInterface definition.
   *
   * @var \GuzzleHttp\ClientInterface
   */

  protected $httpClient;


  /**
   * The base URL for the dog API.
   *
   * @var string
   */

  private $baseUrl = 'https://dog.ceo/api/';


  /**
   * {@inheritdoc}
   */

  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->httpClient = $container->get('http_client');
    return $instance;
  }


}

Creating the controller methods

Now we just add inside of that the methods we want to have run for each page. They will each return a render array of the page we will display. We will create two methods for our example, one for a random picture of any dog, get() and one for a random picture of a dog of a determined breed, getByBreed($breed).

So now our RandomDoggoController.php should look like this:

<?php


namespace Drupal\random_doggo\Controller;


use Drupal\Component\Serialization\Json;
use GuzzleHttp\Exception\ClientException;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;


/**
 * Class RandomDoggoController.
 */

class RandomDoggoController extends ControllerBase {


​   /**
   * GuzzleHttp\ClientInterface definition.
   *
   * @var \GuzzleHttp\ClientInterface
   */

  protected $httpClient;


​   /**
   * The base URL for the dog API.
   *
   * @var string
   */

  private $baseUrl = 'https://dog.ceo/api/';


​   /**
   * {@inheritdoc}
   */

  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->httpClient = $container->get('http_client');
    return $instance;
  }


​   /**
   * Gets a random dog.
   *
   * @return array
   * Render array for the random dog template.
   */

  public function get() {
    // Get a random dog!
    $endpoint = 'breeds/image/random';
    $response = $this->httpClient->get($this->baseUrl . $endpoint);
    $data = Json::decode($response->getBody());


​     return [
      '#type' => 'markup',
      '#markup' => "<img src='{$data['message']}'>",
      '#cache' => [
        'max-age' => 0,
      ],
    ];
  }


​   /**
   * Gets a random dog, of a specific breed.
   *
   * @param string $breed
   * The breed of the dog to look for.
   *
   * @return array
   * Render array for the random dog template.
   */

  public function getByBreed($breed) {
    try {


​       // Get a random dog!
      $endpoint = "breed/$breed/images/random";
      $response = $this->httpClient->get($this->baseUrl . $endpoint);
    }
    catch (ClientException $e) {
      // Return a 404 if an error occured.
      throw new NotFoundHttpException();
    }


​     $data = Json::decode($response->getBody());


​     return [
      '#type' => 'markup',
      '#markup' => "<img src='{$data['message']}'>",
      '#cache' => [
        'max-age' => 0,
      ],
    ];
  }


​ }

Criar a nossa rota

We have the methods, now we need to tell Drupal when he should run these methods. We can do that with a routing file. It will basically tell Drupal “If you visit this page, then let our Controller method handle what’s shown to the user”.

We place the routing file inside our module folder.

So we get random_doggo.routing.yml:

random_doggo.random_doggo_controller_get:
  path: '/doggo'
  defaults:
    _controller: '\Drupal\random_doggo\Controller\RandomDoggoCont

roller::get'

    _title: 'Random Doggo!'
  requirements:
    _permission: 'access content'
random_doggo.random_doggo_controller_getByBreed:
  path: '/doggo/{breed}'
  defaults:
  _controller: '\Drupal\random_doggo\Controller\RandomDoggoContro

ller::getByBreed'

  _title: 'Random Doggo by Breed!'
  requirements:
  _permission: 'access content'

This tells Drupal that, whenever we visit /doggo on our site we will run our Controller’s get() method, and display a random picture of a dog. And also that by visiting /doggo/hound, we will instead call our Controller’s getByBreed() method and get a random picture of a determined breed of dog. The user can input any other breed on the URL. Our function will then receive it as the $breed.

Enable your module

Very important, don’t forget to enable your module. Either through the backoffice or by running drush en random_doggo.

Play around with Controllers

We could further improve this example Controller by adding a custom template, instead of just plain old markup for the render array.

You could also use a Controller for literally anything in Drupal. You can see it as a custom page in Drupal where you can run your own custom code. In this example we return a render array, but you can also return just an HTTP Response.

We can have a Controller, routed to /unpublish/last-week for unpublishing every node older than one week, for example. We can use permissions in the routing file to make sure only a certain role has access to the Controller method. For example, in our previous routing file, if instead of _permission: 'access content', we had _role: 'editor', then only users with the editor role would be able to access it.

As you can see, Controllers can be very versatile, and they are a great tool in the arsenal for developers to bypass the Drupal backoffice and utilize their own code to create a custom functionality.