Drupal 8/9 - Additional routes using custom module
Tags: drupal, cms, php, webdev
Published: 2021-11-03 16:39:46

Drupal is a really powerful CMS and it offers a lot of extendibility out of the box, mainly thanks to Symfony. One of the basic tasks we can archive is the creation of custom endpoints associated to a controller, in addition to the system ones generated by the definition and creation of content. The controller can then provide a response in different formats (HTML markup, JSON, Twig, etc.).
For the first step we need to create a new module and define a *MY_MODULE.routing.yml* file in which specify the url, the name of the controller and the access permissions.

## Module creation

We use `drupal` console command in order to create a new module:

```bash
drupal generate:module
```

After answering the questions (use all the defaults, except the unit test class creation), these files will be generated in the folder *modules/custom/MACHINE_NAME_MY_MODULE* (*composer.json* is optional):

```bash
├── MACHINE_NAME_MY_MODULE.info.yml
├── MACHINE_NAME_MY_MODULE.module
├── composer.json
└── templates
    └── MACHINE_NAME_MY_MODULE.html.twig
```


It is also possible to run the command without wizard by passing directly the options. Refer to the guide [generate:module | Drupal Console](https://drupalconsole.com/docs/en/commands/generate-module)

## Module installation

At this point we install the new module with the command:

```bash
drupal module:install MACHINE_NAME_MY_MODULE
```


Alternatively we can use the Drupal admin UI at */admin/modules.*

## Controller creation

To create the controller associated to the module, use the command:

```bash
drupal generate:controller
```

Answer the questions:
1. module name (enter the one previously created)
2. controller class name
3. controller method title
4. action method name
5. route path (related to the point 4)   

Points from 3 to 5 are repeated until you leave a blank method title (so you can add several methods/routes in one shot).
At the end of the procedure these files will be generated:

```bash
├── MACHINE_NAME_MY_MODULE.routing.yml
└── src
    └── Controller
        └── CONTROLLER_NAME.php
```

Also in this case it is possible to run the command without a wizard.
Refer to the guide [generate:controller | Drupal Console](https://drupalconsole.com/docs/en/commands/generate-controller)

## Output data with Twig

If you visit the url set in the controller you will receive as response the default output generated by the `generate:controller` command.

```html
Implement method: METHOD_NAME
```

To render the Twig template already present in the templates subdirectory of the module, you have to modify the method created in the controller like this (in this example the method name created in the previous step is *content*) 

from this:

```php
public function content() {
  return [
    '#type' => 'markup',
    '#markup' => $this->t('Implement method: content')
  ];
}
```

to this:

```php
public function content() {
  return [
    '#theme' => 'MACHINE_NAME_MY_MODULE'
  ];
}
```

## Passing variables to the template

If you want to pass variables from the controller to the Twig template, you must edit the *MACHINE_NAME_MY_MODULE.module* file to list the variables you want to use, for example like this:

```php
function MACHINE_NAME_MY_MODULE_theme() {
  return [
    'MACHINE_NAME_MY_MODULE' => [
      'render element' => 'children',
      'variables' => [
        'var1' => '',
        'items' => []
      ]
    ],
  ];
}
```


Then in the controller *content* method we go to value the two variables *var1* and *items*:

```php
  public function content() {
    $var1 = 'Hello world!';
    $array1 = [1, 2, 3, 4];

    return [
      '#theme' => 'MACHINE_NAME_MY_MODULE',
      '#var1' => $var1,
      '#items' => $array1,
    ];
  }
```

Finally clean up the cache with the command:

```bash
drupal cc
```

Now we have access to the *var1* and *items* variables directly in the Twig template, as in the following example:

```twig
<div>
  <p>{{ var1 }}</p>
  {% for item in items %}
    {{ item }}
  {% endfor %}
</div>
```

## Change access role

Using the controller generation command, default access is allowed to all users via this rule in the *MACHINE_NAME_MY_MODULE.routing.yml* file:

```yaml
requirements:
  _permission: 'access content'
```

For example, if we want to restrict access to authenticated users only, we have to edit the content of the file as follows:

```yaml
requirements:
  _permission: 'access content'
  _role: 'authenticated'
```

It is possible to specify multiple values using "," as a separator if you want to use AND logic, otherwise use "+" for OR logic, as in the following example:

```yaml
requirements:
  _permission: 'access content'
  _role: 'authenticated, editor'
```

## Change caching option

Starting from Drupal 8.9, the output of the views of a custom module is automatically cached. If you want to disable this behavior, for example if your content is dynamic, add the *no_cache* option in the *MY_MODULE.routing.yml* file:

```yaml
requirements:
  _permission: 'access content'
  _role: 'authenticated'
options:
  no_cache: TRUE
```