Build a Drupal 8 Module: Routing, Controllers and Menu Links

Share this article

Please be aware that due to the development process Drupal 8 has been undergoing at the time of writing, some parts of the code might be outdated. Take a look at this repository in which I try to update the example code and make it work with the latest Drupal 8 release.

Drupal 8 brings about a lot of changes that seek to enroll it in the same club other modern PHP frameworks belong to. This means the old PHP 4 style procedural programming is heavily replaced with an object oriented architecture. To achieve this, under the initiative of Proudly Found Elsewhere, Drupal 8 includes code not developed specifically for Drupal.

One of the most important additions to Drupal are Symfony components, with 2 major implications for Drupal developers. First, it has the potential to greatly increase the number of devs that will now want to develop for Drupal. And second, it gives quite a scare to some of the current Drupal 7 developers who do not have much experience with modern PHP practices. But that’s ok, we all learn, and lessons taken from frameworks like Symfony (and hopefully Drupal 8), will be easily extensible and applicable to other PHP frameworks out there.

In the meantime, Drupal 8 is in a late stage of its release cycle, the current version at the time of writing being alpha11. We will use this version to show some of the basic changes to module development Drupal 7 devs will first encounter and should get familiar with. I set up a Git repo where you can find the code I write in this series so you can follow along like that if you want.

How do I create a module?

The first thing we are going to look at is defining the necessary files and folder structure to tell Drupal 8 about our new module. In Drupal 7 we had to create at least 2 files (.info and .module), but in Drupal 8, the YAML version of the former is enough. And yes, .info files are now replaced with .info.yml files and contain similar data but structured differently.

Another major change is that custom and contrib module folders now go straight into the root modules/ folder. This is because all of the core code has been moved into a separate core/ folder of its own. Of course, within the modules/ directory, you are encouraged to separate modules between custom and contrib like in Drupal 7.

Let’s go ahead and create a module called demo (very original) and place it in the modules/custom/ directory. And as I mentioned, inside of this newly created demo/ folder, all we need to begin with is a demo.info.yml file with the following required content:

name: Drupal 8 Demo module
description: 'Demo module for Drupal 8 alpha11'
type: module
core: 8.x

Three out of four you should be familiar with (name, description and core). The type is now also a requirement as you can have yml files for themes as well. Another important thing to keep in mind is that white spaces in yml files mean something and proper indentation is used to organize data in array-like structures.

You can check out this documentation page for other key|value pairs that can go into a module .info.yml file and the change notice that announced the switch to this format.

And that’s it, one file. You can now navigate to the Extend page, find the Demo module and enable it.

As I mentioned, we are no longer required to create a .module file before we can enable the module. And architecturally speaking, the .module files will be significantly reduced in size due to most of the business logic moving to service classes, controllers and plugins, but we’ll see some of that later.

What is ‘routing’ and what happened to hook_menu() and its callbacks?

In Drupal 7, hook_menu() was probably the most implemented hook because it was used to define paths to Drupal and connect these paths with callback functions. It was also responsible for creating menu links and a bunch of other stuff.

In Drupal 8 we won’t need hook_menu() anymore as we make heavy use of the Symfony2 components to handle the routing. This involves defining the routes as configuration and handling the callback in a controller (the method of a Controller class). Let’s see how that works by creating a simple page that outputs the classic Hello world!.

First, we need to create a routing file for our module called demo.routing.yml. This file goes in the module root folder (next to demo.info.yml). Inside this file, we can have the following (simple) route definition:

demo.demo:
  path: '/demo'
  defaults:
    _content: '\Drupal\demo\Controller\DemoController::demo'
    _title: 'Demo'
  requirements:
    _permission: 'access content'

The first line marks the beginning of a new route called demo for the module demo (the first is the module name and the second the route name). Under path, we specify the path we want this route to register. Under defaults, we have two things: the default page title (_title) and the _content which references a method on the DemoController class. Under requirements, we specify the permission the accessing user needs to have to be able to view the page. You should consult this documentation page for more options you can have for this routing file.

Now, let’s create our first controller called DemoController that will have a method named demo() getting called when a user requests this page.

Inside the module directory, create a folder called src/ and one called Controller/ inside of it. This will be the place to store the controller classes. Go ahead and create the first one: DemoController.php.

The placement of the Controllers and, as we will see, other classes, into the src/ folder is part of the adoption of the PSR-4 standard. Initially, there was a bigger folder structure we had to create (PSR-0 standard) but now there is a transition phase in which both will work. So if you still see code placed in a folder called lib/, that’s PSR-0.

Inside of our DemoController.php file, we can now declare our class:

<?php
/**
 * @file
 * Contains \Drupal\demo\Controller\DemoController.
 */

namespace Drupal\demo\Controller;

/**
 * DemoController.
 */
class DemoController {
  /**
   * Generates an example page.
   */
  public function demo() {
    return array(
      '#markup' => t('Hello World!'),
    );
  }      
}

This is the simplest and minimum we need to do in order to get something to display on the page. At the top, we specify the class namespace and below we declare the class.

Inside the DemoController class, we only have the demo() method that returns a Drupal 7-like renderable array. Nothing big. All we have to do now is clear the caches and navigate to http://example.com/demo and we should see a Drupal page with Hello World printed on it.

In Drupal 7, when we implement hook_menu(), we can also add the registered paths to menus in order to have menu links showing up on the site. This is again no longer handled with this hook but we use a yml file to declare the menu links as configuration.

Let’s see how we can create a menu link that shows up under the Structure menu of the administration. First, we need to create a file called demo.menu_links.yml in the root of our module. Inside this yml file we will define menu links and their position in existing menus on the site. To achieve what we set out to do, we need the following:

demo.demo:
  title: Demo Link
  description: 'This is a demo link'
  parent: system.admin_structure
  route_name: demo.demo

Again we have a yml structure based on indentation in which we first define the machine name of the menu link (demo) for the module demo (like we did with the routing). Next, we have the link title and description followed by the parent of this link (where it should be placed) and what route it should use.

The value of parent is the parent menu link (appended by its module) and to find it you need to do a bit of digging in *.menu_links.yml files. I know that the Structure link is defined in the core System module so by looking into the system.menu_links.yml file I could determine the name of this link.

The route_name is the machine name of the route we want to use for this link. We defined ours earlier. And with this in place, you can clear the cache and navigate to http://example.com/admin/structure where you should now see a brand new menu link with the right title and description and that links to the demo/ path. Not bad.

Conclusion

In this article we began exploring module development in Drupal 8. At this stage (alpha11 release), it is time to start learning how to work with the new APIs and port contrib modules. To this end, I am putting in writing my exploration of this new and exiting framework that will be Drupal 8 so that we can all learn the changes and hit the ground running when release day comes.

For starters, we looked at some basics: how you start a Drupal 8 module (files, folder structure etc), all compared with Drupal 7. We’ve also seen how to define routes and a Controller class with a method to be called by this route. And finally, we’ve seen how to create a menu link that uses the route we defined.

In the next tutorial, we will continue building this module and look at some other cool new things Drupal 8 works with. We will see how we can create blocks and how to work with forms and the configuration system. See you then.

Frequently Asked Questions (FAQs) about Building a Drupal 8 Module

What is the basic structure of a Drupal 8 module?

A Drupal 8 module is essentially a set of files that contain some functionality and is structured in a specific way. The basic structure includes a .info.yml file, which provides metadata about the module, a .module file, which contains PHP code, and other optional files like .css, .js, .twig, etc. for additional functionalities. The .info.yml file is mandatory and it defines the name, description, package, type, core, and dependencies of the module.

How does the routing system work in Drupal 8?

The routing system in Drupal 8 is responsible for mapping URLs to specific controller classes. It uses Symfony’s routing component, which allows for a flexible and powerful system. The routing system uses a ‘routing.yml’ file to define routes with a unique name, path, defaults, and requirements. The defaults typically specify a controller class that is responsible for handling the request.

How can I create a custom page in Drupal 8?

Creating a custom page in Drupal 8 involves defining a route and a controller. The route is defined in the ‘routing.yml’ file and maps a URL to a controller. The controller is a PHP class that returns a render array for the page content. The controller class should be placed in the ‘src/Controller’ directory of your module.

How can I add a menu link to my custom page in Drupal 8?

Adding a menu link to your custom page involves creating a ‘links.menu.yml’ file in your module. This file defines menu links with properties like title, description, parent, route name, and weight. The route name should match the one defined in your ‘routing.yml’ file.

How can I create a form in Drupal 8?

Creating a form in Drupal 8 involves creating a form class that extends the ‘FormBase’ class and implements the ‘FormInterface’. This class defines the form elements, validation, and submission handling. The form can be displayed on a page by returning it from a controller.

How can I create a block in Drupal 8?

Creating a block in Drupal 8 involves creating a block class that extends the ‘BlockBase’ class. This class defines the block content and other properties. The block can be placed on a page through the block layout interface.

How can I create a configuration form in Drupal 8?

Creating a configuration form involves creating a form class that extends the ‘ConfigFormBase’ class and implements the ‘FormInterface’. This class defines the form elements and handles the saving and loading of configuration data.

How can I create an admin page in Drupal 8?

Creating an admin page involves defining a route with a path under ‘/admin’ and a controller that returns a render array for the page content. The page can be added to the admin menu by defining a menu link with a parent under ‘system.admin’.

How can I create a theme in Drupal 8?

Creating a theme in Drupal 8 involves creating a .info.yml file that defines the name, description, type, core, and base theme. Additional .twig, .css, and .js files can be added for theming individual elements.

How can I create a custom field type in Drupal 8?

Creating a custom field type involves creating a field type class that extends the ‘FieldTypePluginBase’ class. This class defines the field properties and methods for storage, display, and form handling. The field type can be used in content types, users, comments, and other entities.

Daniel SiposDaniel Sipos
View Author

Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.

controllerdrupaldrupal moduledrupal-planetdrupal8drupal8. modulemenu linksOOPHPPHProuting
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week