If you want to define a new URL that a user can navigate to, and custom PHP code that will generate the content of the page, you need a route and a controller. Most of the time you'll want to do something more complex than hard code the content of the page. This will require using services in your controller. This can be accomplished in different ways.
In this tutorial we'll:
- Provide the definition for a new route which maps a path to the callback method of a controller class.
- Create a controller that returns a hard coded string.
- Look at examples of using both
ControllerBase
and dependency injection to access services from a controller, and discuss the benefits of both approaches.
By the end of this tutorial, you should be able to define a new route that maps to a controller and displays content on the page as a result of your custom logic.
The routing system can get dynamic values from the URL and pass them as arguments to the controller. This means a single route with a path of /node/{node}
can be used to display any node entity. Route parameters can be validated, and upcast to complex data types via parameter conversion. If you ever want to pass arguments to the controller for a route, you'll use parameters to do so.
In this tutorial we'll:
- Define what parameters (slugs, placeholders) are and what they are used for in a route definition.
- Explain how URL parameters are passed to a controller.
- Define parameter upcasting.
By the end of this tutorial, you should be able to explain how to define a route that uses parameters to pass dynamic values to the route controller, and explain how parameter upcasting works.
Every route should define its access control parameters. When you define routes in a module, you can limit who has access to those routes via different access control options. Route-level access control applies to the path. If your route defines a path like /journey/example
, the access control configuration will determine whether to show the current user the page at the path defined by the route, or to have Drupal serve an "HTTP 403 Access Denied" message instead.
In this tutorial we'll look at different ways of adding access control to a route including:
- Access based on the current user's roles and permissions
- Access based on custom logic in a callback method
- Logic in an access checker service
By the end of this tutorial, you should be able to add access control logic to your custom routes that will meet any requirement.
When defining a route and subsequently displaying the page, often we need to calculate the page title based on route parameters or other logic. In these cases, we can't just hard code the value into the _title
configuration of the route. To set a dynamic title for a page, we'll use the route's _title_callback
option, and point to a PHP callback that contains the logic that computes the title of the page.
In this tutorial we'll:
- Learn how to use the
_title_callback
route configuration option to dynamically set a page title - Explain how arguments are provided to the title callback method
- Update the route and controller from a previous tutorial to use a dynamic title callback
By the end of this tutorial, you should be able to configure a route so that its title can be set dynamically using route parameters, instead of hard-coding the title with a static string of text.
Let's write some code that will allow us to see route parameters in action. We'll define a new route with a path like /journey/42/full but where 42
can be any node ID, and full
can be any view mode. When a user accesses the path we'll pass the dynamic parameters from the URL to the controller. The controller will then load the corresponding node and render it using the provided view mode, and return that to display on the page.
By the end of this tutorial you should be able to:
- Use dynamic slugs in a route to pass parameters to the route controller.
- See how Drupal will upcast a value like the node ID, 42, to a
Node
object automatically. - Explain what happens when you visit a URL that matches a route but the parameters don't pass validation.
By the end of this tutorial, you should be able to pass dynamic values from the URL to a route's controller.
Every web framework, including Drupal, has basically the same job: provide a way for developers to map URLs to the code that builds the corresponding pages. Drupal uses Symfony's HTTPKernel component. Kernel events are dispatched to coordinate the following tasks:
- Process the incoming request
- Figure out what to put on the page
- Create a response
- Deliver that response to the user's browser
Knowing a bit more about how Drupal handles the request-to-response workflow will help you better understand how to use routes and controllers to create your own custom pages or deal with authentication, access checking, and error handling in a Drupal module.
In this tutorial we'll:
- Walk through the process that Drupal uses to convert an incoming request into HTML that a browser can read
- See how the Symfony
HTTPKernel
helps orchestrate this process - Learn about how the output from a custom controller gets incorporated into the final page
By the end of this tutorial, you should be able to describe the process that Drupal goes through to convert an incoming request for a URL into an HTML response displayed by the browser.
Sometimes we need the response returned from a controller to be something other than HTML content wrapped with the rest of a Drupal theme. Maybe we need to return plain text, or JSON structured data for an application to consume. Perhaps we need greater control over the HTTP headers sent in the response. This is possible by building on the fact that controllers can return generic Response
objects instead of renderable arrays, allowing you to gain complete control over what is sent to the requesting agent.
In this tutorial we'll:
- Look at how to return a plain text response, and JSON data
- Show how to make your responses cacheable by adding cacheability metadata
- Learn about how to use a generic Symfony
Response
to gain greater control over what gets returned
By the end of this tutorial, you should be able to return responses from a controller in a Drupal module that are not HTML content wrapped in a Drupal theme.
By default, individual forms in Drupal are not output using Twig template files. It's possible to associate a form with a Twig template file by creating a new theme hook, and then referencing that theme hook from the $form
array that defines the form. Doing so allows theme developers to customize the layout of the elements in the form using HTML and CSS.
This is useful when you want to change the layout of the entire form. For example, putting the elements into 2 columns. If you want to change individual elements in the form, you can often do so by overriding element specific Twig template files.
In this tutorial, we'll:
- Learn how to create a new theme hook that can be used to theme an element in a render array.
- Associate the
$form
we want to theme with the new theme hook we created. - Create a Twig template file for the theme hook that will allow us to lay out the form elements using custom HTML.
By the end of this tutorial, you should be able to associate a Twig template file with any form in Drupal, so that you can customize its layout using HTML and CSS.
Tools like PHP_Codesniffer (phpcs) can be used to help ensure your code adheres to Drupal's coding standards. As a module developer, you should use phpcs and its Drupal-specific rule sets on all custom module code.
In this tutorial, we'll:
- Learn about PHP_Codesniffer (phpcs).
- Install PHP_Codesniffer and the Drupal-specific rules.
- Use phpcs to lint our custom code.
By the end of this tutorial, you should be able to use PHP_Codesniffer to help automate the process of adhering to Drupal's coding standards.
Coding standards are vital in collaborative environments like Drupal. These guidelines ensure consistency, readability, and maintainability of the codebase. Drupal has its own coding standards that outline best practices and formatting guidelines, promote code quality, simplify code reviews, and enhance the project's overall health.
In this tutorial, we'll:
- Explore the use of coding standards in Drupal.
- Highlight basic practices and sources for detailed information.
By the end of this tutorial you should be able to explain why coding standards are important and where to find more info about Drupal's coding standards.
To use a hook in a Drupal module, we need to add a function with a specific naming convention to your module's MODULE_NAME.module file. Each hook has unique arguments and an expected return value. In this tutorial, we'll walk through the process of implementing a hook by adding end-user help text for the anytown module, which Drupal's administrative UI will display. The process we'll use here applies to any hook implementation.
In this tutorial, we'll:
- Locate the documentation for
hook_help()
. - Implement the hook in the anytown module.
- Verify our hook implementation.
By the end, you'll have implemented hook_help()
to display help text in the Drupal UI.
Update hooks in Drupal are used for executing database updates or applying configuration changes during module updates. They ensure these changes occur once and in the correct order.
In this tutorial, we'll:
- Explore the purpose of update hooks.
- Learn how to implement update hooks for database updates or configuration changes.
By the end of this tutorial, you should be able to understand the use case for update hooks and know how to get started implementing one.
It's a Drupal best practice to always use Drupal's internationalization utilities for any user interface strings in your code. This includes the PHP t()
function and StringTranslationTrait
trait, the Twig t
filter, and the JavaScript Drupal.t()
function. This makes it possible for our module's interface to be localized.
In this tutorial, we'll:
- Edit the
WeatherPage
controller and use thet()
method from theStringTranslationTrait
trait for all UI strings. - Update the weather-page.html.twig template file to use the Twig
t
filter. - Modify the JavaScript in our forecast.js code to use the
Drupal.t()
function for UI strings.
By the end of this tutorial you should be able to update the PHP, Twig, and JavaScript code in your module to ensure that any user interface strings they output are translatable.
Entities are the building blocks of Drupal's data structures. As module developers, the Entity API provides a way to manage custom data with minimal code. You'll use it when altering or enhancing existing content or when managing custom data sets. Instead of writing SQL, you'll be using the Entity API to manage data within a Drupal application.
In this tutorial, we'll:
- Define entities and their significance in Drupal.
- Distinguish between content entities and configuration entities.
- Explore entity-related terminology such as bundles, fields, annotations, plugins, and handlers.
By the end of this tutorial, you'll have a foundational understanding of the Entity API and how it's used for data management in Drupal.
Validation happens whenever an entity is created or updated, ensuring data integrity across form submissions, JSON:API requests, and direct entity object manipulation. Drupal's Entity Validation API, consists of constraints, validators, and their integration. As module developer, we'll use this API to enforce custom rules about what constitutes valid data.
In this tutorial, we'll:
- Learn about the roles of constraints and validators within Drupal's validation system.
- See how to create and integrate custom validation rules.
- Apply custom validation to an entity type that enforces specific data integrity rules.
By the end of this tutorial, you should be able to define new constraints and validators, and associate them with entity types.
We must validate user input entered in forms to ensure data integrity and security. In our Anytown module, we need to verify that the location input for the weather forecast API is a valid 5-digit ZIP code.
In this tutorial, we'll:
- Add a
validateForm()
method to the form controller. - Ensure the
location
field contains a 5-digit ZIP code. - Display an error if validation fails.
By the end of this tutorial, you'll know how to add custom validation logic to a form.
In the world of Drupal development, ensuring the security of custom code is paramount. Drupal's security standards and features offer a robust foundation, but developers must also adhere to security best practices to safeguard against vulnerabilities. From sanitizing output to prevent cross-site scripting (XSS) attacks to using Drupal's database abstraction layer to avert SQL injection, Drupal empowers developers to write secure code. But it's still up to you, the developer, to do so.
In this tutorial, we'll:
- Provide an overview of these practices.
- Introduces additional resources to increase your security expertise.
By the end of this tutorial, you should be able to articulate some of the common security risks and mitigation strategies for Drupal modules.
Concept: Caching
FreeCaching is an essential piece of website performance and user experience. Drupal's Cache API enables you as a module developer to specify cacheability information for data rendered through the Render API. By storing previously calculated data or page renderings, Drupal can skip complex backend processes for subsequent requests and deliver content faster. Drupal's Cache API provides services to store, retrieve, and invalidate cached data.
In this tutorial, we'll:
- Learn why caching is important.
- Cover key terms and concepts associated with the Cache API.
- Explore how and when custom modules can implement caching.
By the end of this tutorial, you should understand the core features of Drupal's Cache API and when to use them.
The logic for retrieving and processing a weather forecast is hard-coded into the WeatherPage
controller. To make our code more reusable, maintainable, and organized, we can refactor this code into a custom weather forecast API service.
In this tutorial, we'll:
- Define a PHP interface for a weather forecast API service and write an implementation of that service.
- Create a service definition file to inform Drupal about our new service and its dependencies.
- Refactor the
WeatherPage
controller to use the new service, which will clean up duplicate code.
By the end of this tutorial, you'll be able to define and use a custom service in a Drupal module.
We can enhance our site's performance by using Drupal's cache backend service to cache and reuse results from an external API request. We can cache the results of weather forecast API queries within a custom service. This will give us an opportunity to practice implementing Drupal's caching capabilities and optimize the performance of our module.
In this tutorial, we'll:
- Inject the cache backend service into our forecast API service class.
- Implement caching to store and reuse weather forecast API results locally.
By the end of this tutorial, you'll understand how to use Drupal's cache backend service for data caching.