One of the key advantages of Docker is that it makes it much easier to share your containers with your team members. For most of this series, we've been relying on containers hosted on Docker Hub. When we need to create a custom container for our team, we want to leverage that same sharing infrastructure.
In this tutorial, we'll:
- Describe the various methods of sharing containers.
- Outline the advantages of using Docker Hub.
- Briefly describe why and when you should use a private container image registry.
For many container images on Docker Hub, the preferred approach is to create an automatic build. An automatic build integrates Hub with a public Git repository, providing you an effective, open, and best-practice approach to sharing your containers with your team, and with the world.
In this tutorial, we'll:
- List the advantages of creating an automatic build compared to other approaches on Docker Hub.
- Describe the process of creating an automatic build.
- Outline how to organize your git repository for your images.
- Learn how to configure and trigger the build on Hub.
Often it's useful for a container image to provide several variants of itself under the same name on Docker Hub. Docker uses tags to identify these variants. You can configure your own tags as part of your automatic build on Docker Hub.
In this tutorial, we'll:
- Outline the uses for tags
- Discuss the best practices for tag names
- Learn how to add tags to your automatic build on Docker Hub
Docker Hub provides a free, easy to use way of distributing your container images. However, there are situations where sharing your container images is either not ideal, bad practice, or against legal requirements. In those cases, you will want to use a private registry instead.
In this tutorial, we'll:
- Describe what a private registry is.
- Learn how to run Docker's own
registry
image. - Learn how to push and pull images from the self-hosted registry.
- Outline the production concerns for the
registry
image. - List other options for self-hosting your own images.
Docker provides numerous advantages for us as Drupal developers. It simplifies the management of infrastructure for our projects while allowing customization to suit each project's needs. Running Docker in production also brings a number of advantages, but it also creates new concerns.
In this tutorial, we'll:
- Outline the advantages Docker brings to the production environment.
- Highlight the concerns when planning a production deployment of Docker.
- Describe container orchestration and list several container orchestration platforms to choose from.
Drupal's Breakpoint module defines a "breakpoint" plugin type that modules or themes can implement via a breakpoints configuration file. So, in order to make their breakpoints discoverable, themes and modules define their breakpoints in a THEME-OR-MODULE.breakpoints.yml file located in the root of their directory.
In this tutorial, you'll learn about the structure of a breakpoints configuration file and why you would want to use one. We’ll cover the kinds of metadata you can include in a breakpoint file, including key, label, mediaQuery, weight, multipliers, and breakpoint group. Throughout, we'll look at some examples of breakpoint configuration files available in Drupal themes and modules.
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.