Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Drupal 10 Module Development
Drupal 10 Module Development

Drupal 10 Module Development: Develop and deliver engaging and intuitive enterprise-level apps , Fourth Edition

eBook
$31.99 $35.99
Paperback
$44.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing
Table of content icon View table of contents Preview book icon Preview Book

Drupal 10 Module Development

Creating Your First Module

Now that we have covered some of the introductory aspects of Drupal module development, it’s time to dive right into the meat of what we are doing here—module creation.

Here are some of the important topics that we will cover in this chapter:

  • Creating a new Drupal module – the files that are necessary to get started
  • Creating a route and controller
  • Creating and using a service
  • Creating a form
  • Creating a custom block
  • Working with links
  • Using the Event Dispatcher

Concretely, in this chapter, we will create a new custom module called Hello World. In this module, we will define a route that maps to a controller and that outputs this age-old programming message. This will be our first win.

Next, we will define a service that our Controller will use to pimp out our message. After all, we don’t want the same message presented to the user all day long. This simple example, however, will illustrate what services are and how to interact with the service container to make use of them.

Then, we will create a form where an administrator will be able to override the message shown on our page. It will be stored in configuration, and we will alter our service to make use of that configuration. The key takeaway here will be the use of the Form API. However, we will also discuss how to store some basic configuration values and add dependencies to our existing services.

Finally, we want to become a bit more flexible. Why should users only be greeted on a specific page? We will create a custom block that can be placed anywhere on the site and will display the same message. Here, we will see how block plugins are defined and how they can expose their own configuration forms to be more flexible.

Although not strictly related to our Hello World example, we will also look at how to work with links programmatically in Drupal. This is a very common task that any Drupal developer needs to do very often. This is why we will get in it out of the way early.

Moreover, we will also look at using the Event Dispatcher component and, more importantly, subscribing to events. We’ll illustrate this with a fairly common example of when you’d need to do this—performing redirects from incoming requests.

By the end of this chapter, you should have the foundational knowledge necessary to build your own module from scratch. Moreover, you should be able to understand and implement some of the most used techniques in Drupal module development.

Creating a module

Creating a simple Drupal module is not difficult. You only need one file for it to be recognized by the core installation and to be able to enable it. In this state, it won’t do much, but it will be installable. Let’s first look at how to do this, and then we will progressively add meat to it in order to achieve the goals set out at the beginning of the chapter.

Modules go inside the /modules folder of the Drupal application. Inside the /modules folder, there can be a /contrib folder, which stores contributed modules, and a /custom folder, where we put the modules we write custom for the specific application. And that is where we will place our custom module, called Hello World.

We will start by creating a folder called hello_world. This will also be the module’s machine name used in many other places. Inside, we will need to create an info file that describes our module. This file is named hello_world.info.yml. This naming structure is important—first, the module name, then info, followed by the .yml extension. You will hear this file often referred to as the module’s info file (due to it having had the .info extension in past versions of Drupal).

Inside this file, we will need to add some minimal information that describes our module. We will go with something like this:

name: Hello World
description: Hello World module
type: module
core_version_requirement: ^10
package: Custom

Some of this is self-explanatory, but let’s see what these lines mean:

  • The first two key-value pairs represent the human-readable name and description of the module.
  • The type key means that this is a module info file rather than a theme.
  • The core_version_requirement key specifies that this module works with version 10 of Drupal, and it won’t be installable on previous or future versions.
  • Finally, we place this in a generic Custom package so that it gets categorized in this group on the modules’ administration screen.

That is pretty much it. The module can now be enabled either through the UI at /admin/modules or via Drush using the drush en hello_world command.

Note

Using the core_version_requirement key, we can semantically specify which version of Drupal the module works with. For example, this would indicate the module is compatible with Drupal 9 as well: ^9.5 || ^10.

Before we move on, let’s see what other options you can add (and probably will need to add at some point or another) to the info file:

Module dependencies: If your module depends on other modules, you can specify this in its info file like so:

dependencies:
  - drupal:views
  - ctools:ctools

The dependencies should be named in the project:module format, where project is the project name as it appears in the URL of the project on Drupal.org and module is the machine name of the module.

Configuration: If your module has a general configuration form that centralizes the configuration options of the module, you can specify the route of that form in the info file. Doing so will add a link to that form on the admin/modules UI page where modules are being installed:

configure: module_name.configuration_route_name

The module as it stands doesn’t do much. In fact, it does nothing. However, do pat yourself on the back, as you have created your first Drupal module. Before we move on to the interesting stuff we planned out, let’s implement our first hook, responsible for providing some helpful information about our module.

Your first hook implementation

As we hinted at in the first chapter, when Drupal encounters an event for which there is a hook (and there are hundreds of such events), it will look through all modules for matching hook implementations. Now, how does it find the matching implementations? It looks for the functions that are named in the module_name_hook_name format, where hook_name is replaced by the name of the hook being implemented and module_name is the module machine name. The name of a hook is whatever comes after hook_. We will see an example next when we implement hook_help(). However, once it finds the implementations, it will then execute each of them, one after another. Once all hook implementations have been executed, Drupal will continue its processing.

Hook implementations typically go inside a .module file, so let’s create one in our module folder called hello_world.module and place an opening PHP tag at the top. Then, we can have the following hook_help() implementation inside (and typically all other hook implementations):

use Drupal\Core\Routing\RouteMatchInterface;
/**
 * Implements hook_help().
 */
function hello_world_help($route_name, RouteMatchInterface
    $route_match) {
  switch ($route_name) {
    case 'help.page.hello_world':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('This is an example module.') .
    '</p>';
      return $output;
    default:
  }
}

As you can see, the name of the function respects the previously mentioned format—module_name_hook_name—because we are implementing hook_help. So, we replaced hook with the module name and hook_name with help. Moreover, this particular hook takes two parameters that we can use inside it, though in our case, we only use one, that is, the route name.

The purpose of this hook is to provide Drupal with some help text about what this module does. You won’t always implement this hook, but it’s good to be aware of it. The way it works is that each new module receives its own route inside the main module, where users can browse this info—ours is help.page.hello_world. So, in this implementation, we will tell Drupal (and, more specifically, the core Help module) the following: if a user is looking at our module’s help route (page), show the info contained in the $output variable. And that’s pretty much it.

According to the Drupal coding standards, the DocBlock message above the hook implementation needs to contain at least a line in the format you see above. A second line could, in certain cases, document the logic taking place in the hook, especially if we were doing something complex or not immediately obvious. It’s good to help others understand what we are doing.

Users can now reach this page from the module administration page by clicking on the Help link for each individual module that has this hook implemented. Do remember to clear the cache first, though. Easy, right?

Figure 2.1: Hello World example module

Figure 2.1: Hello World example module

Even though we are not really providing any useful info through this hook, implementing it helped us understand how hooks work and what the naming convention is for using them. Additionally, we saw an example of a traditional (procedural) Drupal extension point that module developers can use. In doing so, we literally extended the capability of the Help module by allowing it to give more info to users.

Before we move on, let’s quickly add a file comment to ensure we respect the Drupal coding standards. So, we add the following to the top of the .module file:

/**
 * @file
 * Hello World module file.
 */

Note

In order to keep the code examples on the pages of the book concise, going forward, I will skip certain formatting required for respecting the Drupal coding standards. In the GitHub repository, however, all the code should be fully correct.

Now, let’s move on to creating something of our own.

Route and controller

The first real piece of functionality we set out to create was a simple Drupal page that outputs the age-old Hello World string. To do this, we will need two things—a route and a controller. So, let’s start with the first one.

The route

Inside our module, we will need to create our routing file that will hold all our statically defined routes. The name of this file will be hello_world.routing.yml. By now, I assume that you understand what the deal is with the file naming conventions in a Drupal module. However, in any case, this is another YAML file in which we will need to put YAML-formatted data:

hello_world.hello:
  path: '/hello'
  defaults:
    controller:  Drupal\hello_world\Controller\
        HelloWorldController::helloWorld
    _title: 'Our first route'
  requirements:
    _permission: 'access content'

This is our first route definition. It starts with the route name (hello_world.hello), followed by all the necessary info about it underneath, in a YAML-formatted multidimensional array. The standard practice is to have the route name start with the module name it is in, followed by route qualifiers as needed.

So, what does the route definition contain? There can be many options here, but for now, we will stick with the simple ones that serve our purpose.

Note

For more info about all the route configuration options, visit the relevant documentation page at https://www.drupal.org/docs/drupal-apis/routing-system/structure-of-routes. It is a good resource to keep on hand.

First, we have a path key, which indicates the path we want this route to work on. Then, we have a defaults section, which usually contains info relevant to the handlers responsible for delivering something when this route is accessed. In our case, we set the controller and method responsible for delivering the page, as well as its title. Finally, we have a requirements section, which usually has to do with conditions that need to be met for this route to be accessible (or be hit)—things such as permissions and format. In our case, we will require users to have the access content permission, which most visitors will have. Don’t worry; we will cover more about access in Chapter 10, Access Control.

That is all we need for our first route definition. Now, we will need to create the Controller that maps to it and can deliver something to the user.

Before we do that, let’s look at an example of a very common routing requirement you will most likely have to use really soon. We don’t need this for the functionality we’re building in this chapter, so I won’t include it in the final code. However, it’s important that you know how this works.

Route variables

A very common requirement is to have a variable route parameter (or more) that gets used by the code that maps to the route, for example, the ID or path alias of the page you want to show. These parameters can be added by wrapping a certain path element in curly braces, like so:

path: '/hello/{param}'

Here, {param} will map to a $param variable that gets passed as an argument to the controller or handler responsible for this route. So, if the user goes to the hello/jack path, the $param variable will have the jack value and the controller can use that.

Additionally, Drupal comes with parameter converters that transform the parameter into something more meaningful. For example, an entity can be autoloaded and passed to the Controller directly instead of an ID. Also, if no entity is found, the route acts as a 404, saving us a few good lines of code. To achieve this, we will also need to describe the parameter so that Drupal knows how to autoload it. We can do so by adding a route option for that parameter:

options:
   parameters:
     param:
       type: entity:node

So, we have now mapped the {param} parameter to the node entity type. Hence, if the user goes to hello/1, the node with the ID of 1 will be loaded (if it exists).

We can do one better. If instead of {param}, we name the parameter {node} (the machine name of the entity type), we can avoid having to write the parameters option in the route completely. Drupal will figure out that it is an entity and will try to load that node by itself. Neat, no?

So, keep these things in mind the next time you need to write dynamic routes.

Namespaces

Before moving on with the Controller we set out to write, let’s break down the namespace situation in Drupal and how the folder structure needs to be inside a module.

Drupal uses the PSR-4 namespace autoloading standard. In effect, this means that the namespace of all Drupal core and module classes starts with \Drupal. For modules, the base namespace is \Drupal\module_name, where module_name is the machine name of the module. This then maps to the /src folder found inside the module directory (for main integration files). For PHPUnit tests, we have a different namespace, as we will see in Chapter 17, Automated Testing.

So essentially, we will need a /src folder inside our module to place all our classes that need to be autoloaded. So, we can go ahead and create it.

The Controller

Now that we have found where we must place our Controller, let’s begin by creating a Controller folder inside our module’s /src folder. Although not mandatory, this is standard practice for Controller placement. Inside this folder, we can have our first Controller class file: HelloWorldController.php.

Inside the file, we again have something simple (after the opening PHP tags):

namespace Drupal\hello_world\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
 * Controller for the salutation message.
 */
class HelloWorldController extends ControllerBase {
  /**
   * Hello World.
   *
   * @return array
   *   Our message.
   */
  public function helloWorld() {
    return [
      '#markup' => $this->t('Hello World'),
    ];
  }
}

As expected, we start with the namespace declaration. If you read the previous section, the namespace choice will make sense. Then, we have our Controller class, which extends ControllerBase, which happens to provide some helper tools (such as the StringTranslationTrait, which I will explain later in Chapter 13, Internationalization and Languages). If you recall our route definition, we referenced a helloWorld method on this Controller class.

If you’ve worked with previous versions of Drupal, this array (called a render array) will be familiar. Otherwise, what you need to know right now is that we are returning simple markup with the Hello World text wrapped in the translation service I hinted at in the previous paragraph. After the Controller returns this array, there will be an EventSubscriber that takes this array, runs it through the Drupal theme layer, and returns the HTML page as a response. The actual content returned in the Controller will be wrapped in the Main page content block, which is usually placed in the main content region of the theme.

Now, our simple Controller is done. If we clear the cache and go to /hello, we should encounter a new page that outputs the Our first route title and the Hello World content. Success!

Note

You can clear the cache by going to Admin -> Configuration -> Development -> Performance or by running the drush cache-rebuild command.

Figure 2.2: Controller interface

Figure 2.2: Controller interface

Services

Why don’t I like this approach?

Even if for the moment not much is happening in it, I don’t want the Controller making decisions on how to greet my users. First of all, Controllers need to stay lean. I want my users to be greeted a bit more dynamically, depending on the time of day, and that will increase the complexity. Second of all, maybe I will want this greeting to be done elsewhere as well, and there is no way I am copy-pasting this logic somewhere else, nor am I going to misuse the Controller just to be able to call that method. The solution? We delegate the logic of constructing the greeting to a service and use that service in our Controller to output the greeting.

What is a service?

A service is an object that gets instantiated by a Service Container and is used to handle operations in a reusable way, for example, performing calculations and interacting with the database, an external API, or any number of things. Moreover, it can take dependencies (other services) and use them to help out. Services are a core part of the dependency injection (DI) principle that is commonly used in modern PHP applications.

If you don’t have any experience with these concepts, an important thing to note is also that they are globally registered with the service container and are (usually) instantiated only once per request. This means that altering them after you have requested them from the container means that they stay altered even if you request them again. In essence, they are singletons. So, you should write your services in such a way that they stay immutable, and most of the data they need to process is either from a dependency or passed in from the client that uses it (and does not affect it). Although this is the case for most services, there are some that work differently, in that they get re-created with each request. But these examples are rare, and we should not overload the job at hand by talking about them here.

Note

Many Drupal core service definitions can be found inside the core.services.yml file located in the root /core folder. So, if you are ever looking for service names to use, your best bet is to look there. Additionally, core modules also have service definitions inside their respective *.services.yml files. So, make sure that you also check there.

The HelloWorldSalutation service

Now that we have a general idea as to what a service is, let’s create one to see all this in practice.

As I mentioned earlier, I want my greetings to be more dynamic, that is, I want the salutation to depend on the time of day. So, we will create a HelloWorldSalutation class that is responsible for doing that and place it in the /src folder (our module’s namespace root) in a file naturally called HelloWorldSalutation.php:

namespace Drupal\hello_world;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
 * Prepares the salutation to the world.
 */
class HelloWorldSalutation {
  use StringTranslationTrait;
  /**
   * Returns the salutation
   */
  public function getSalutation() {
    $time = new \DateTime();
    if ((int) $time->format('G') >= 00 && (int) $time->
        format('G') < 12) {
      return $this->t('Good morning world');
    }
    if ((int) $time->format('G') >= 12 && (int) $time->
        format('G') < 18) {
      return $this->t('Good afternoon world');
    }
    if ((int) $time->format('G') >= 18) {
      return $this->t('Good evening world');
    }
  }
}

Note

From now on, I will not always mention the file name that a particular class goes into. So, you can safely assume one file per class, named after the class itself. Moreover, I assume that the namespace business is also clear, so I won’t explain it again.

Let’s see what else we did here. First, we used the StringTranslationTrait in order to expose the translation function. Second, we created a rudimentary method that returns a different greeting depending on the time of day. This could probably have been done better, but for the purposes of this example, it works just fine.

Note

In this example, I used the native PHP time() function to get the current time, and that’s OK. But you should know that Drupal has its very own Drupal\Component\Datetime\Time service that we can use to get the current time. It also has additional methods for requesting time-specific information, so make sure you check it out and use it when appropriate.

Now that we have our class, it’s time to define it as a service. We don’t want to be going new HelloWorldSalutation() all over our code base, but instead, register it with the Service Container and use it from there as a dependency. How do we do that?

First, we will need, yet again, a YAML file: hello_world.services.yml. This file starts with the services key and under that, we will have all the service definitions of our module. So, our file will look like this (for now):

services:
  hello_world.salutation:
    class: Drupal\hello_world\HelloWorldSalutation

This is the simplest possible service definition you can have. You give it a name (hello_world.salutation) and map it to a class to be instantiated. It is standard practice to have the service name start with your module name.

Once we clear the cache, the service will get registered with the Service Container and will be available to use.

Note

If there is any reason to believe that you will have more than one salutation service, you should create an interface that this class can implement. This way, you’ll be able to always type-hint that interface instead of the class and make the implementations swappable. In fact, having interfaces is a best practice.

Tagged services

Service definitions can also be tagged in order to inform the container if they serve a specific purpose. Typically, these are picked up by a collector service that uses them for a given subsystem. As an example, if we wanted to tag the hello_world.salutation service, it would look something like this:

hello_world.salutation:
  class: Drupal\hello_world\HelloWorldSalutation
  tags:
    - {name: tag_name}

Tags can also get a priority, as we will see in some examples later in this book.

Before we go and use our service in the Controller we created, let’s take a breather and run through the ways you can make use of services once they are registered.

Using services in Drupal

There are essentially two ways of using services—statically and injected. The first is done using a static call to the service container, whereas the second uses dependency injection to pass the object through the constructor (or in some rare cases, a setter method). However, let’s check out how, why, and what the real difference is.

Statically, you would use the global \Drupal class to instantiate a service:

$service = \Drupal::service('hello_world.salutation');

This is how we use services in .module files and, in rare cases, classes that are not exposed to the service container and into which we cannot inject.

A few popular services also have shorthand methods on the \Drupal class; for example, \Drupal::entityTypeManager(). I recommend that you inspect the \Drupal class and take a look at the ones with shorthand methods available.

It is not good to use the static method of service instantiation inside a Controller, service, plugin, or any other class where dependency injection is an option. The reason is that it defeats much of the purpose of using a service, as it couples the two, making it a nightmare to test. Inside hook implementations and other Drupal-specific procedural code, on the other hand, we have no choice, and it is normal to do so.

Moreover, just because a piece of code is inside a .module file, it doesn’t mean that it should be there. In general, these modules should only contain things such as hook implementations or any other implementations that require a certain naming convention to be respected. They should also be lean and have their work delegated to services.

The proper way to use services is to inject them where needed. Admittedly, this approach is a bit more time-consuming but, as you progress, it will become second nature. Also, since there are a few different ways to inject dependencies (based on where you do so), we will not cover them here. Instead, we will see how they work throughout this book, at the right time. We will look at a very important example right now in the next section.

Injecting the service into our Controller

Let’s now continue with our module and take a look at how to inject the newly created service into our Controller.

We will need to add some code to the Controller (typically at the beginning of the class so that we can immediately identify the presence of this code when looking at it):

/**
 * @var \Drupal\hello_world\HelloWorldSalutation
 */
protected $salutation;
/**
 * HelloWorldController constructor.
 *
 * @param \Drupal\hello_world\HelloWorldSalutation
      $salutation
 */
public function __construct(HelloWorldSalutation
    $salutation) {
  $this->salutation = $salutation;
}
/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface
    $container) {
  return new static(
    $container->get('hello_world.salutation')
  );
}

In addition to this, ensure that you include the relevant use statements at the top of the file:

use Drupal\hello_world\HelloWorldSalutation;
use Symfony\Component\DependencyInjection
    \ContainerInterface;

So, what is going on here? First, we give the Controller a constructor method, which takes our service as an argument and stores it as a property. For me, this is usually the very first method in the class. But how does this constructor get its argument? It gets it via the create() method, which receives the Service Container as a parameter and is free to choose the service(s) needed by the Controller constructor. This is usually my second method in a class. I prefer this order because it’s very easy to check whether these methods are present. Also, their presence is important, especially when inheriting and observing what the parent is injecting.

OK, but how does this injection business work in reality?

In a nutshell, after the route is found and the responsible Controller is resolved, a check is made to see whether the latter implements ContainerInjectionInterface. Our Controller does so via its parent, ControllerBase. If it does, the Controller gets instantiated via the create() method, and the container is passed to it. From there, it is responsible for creating a new static version of itself with the required services from the container—not that complicated, really!

The create() method is a staple practice in the Drupal dependency injection pattern, so you will see it quite a lot. However, one thing to keep in mind is that you should never pass the entire container to the class you instantiate with it because you are no longer doing dependency injection then.

A note about ControllerBase, which we are extending—it is a standard practice to extend it, but not mandatory, as controllers are nothing more than simple callables. It provides some nice traits, implements the interfaces that are required, and immediately shows what the purpose of the class is. However, from the point of view of dependency injection, I advise against using the helper methods that return services (for example, entityTypeManager()). They, unfortunately, load services statically, which is not the best practice in this case. You should instead inject them yourself, as we did just now.

OK, let’s turn back to our example. Now that we have the service injected, we can use it to render the dynamic salutation:

return [
  '#markup' => $this->salutation->getSalutation(),
];

There we have it. Now our greeting is dependent on the time of day and our Controller is dependent on our salutation service.

One thing I would like to specify about our example is that I disregarded caching for the sake of simplicity. With caching turned on, the page would be cached and served with potentially the wrong salutation. However, in Chapter 11, Caching, we will cover all these intricacies, so there is no point in complicating our example now.

Invoked Controllers

Now that we know what routes, Controllers, and services are, I’d also like to quickly note that Controllers can be defined as services and invoked by the routing system. In other words, just as we defined our hello_world.salutation service, we could define another one that would act as a Controller and reference that service ID in the routing file instead of the fully qualified class name. Then, for Drupal to know which method inside the service to call when a user accesses the route, we would need to implement the magic __invoke method inside the service. The rest would work pretty much in the same way.

This capability was introduced in Drupal 8.7 and is typical of the Action-Domain-Responder architectural pattern. We won’t use it going forward but it’s good to know that it’s available.

The Form API

Our page displays a greeting dynamically, depending on the time of day. However, we now want an administrator to specify what the greeting should actually be, in other words, to override the default behavior of our salutation if they so choose.

The ingredients for achieving this will be as follows:

  • A route (a new page) that displays a form where the administrator can set the greeting
  • A configuration object that will store the greeting

In building this functionality, we will also look at how to add a dependency to our existing service. So, let’s get started with our new route, which naturally goes inside the hello_world.routing.yml file we have already created:

hello_world.greeting_form:
  path: '/admin/config/salutation-configuration'
  defaults:
    _form: Drupal\hello_world\Form\SalutationConfigurationForm
    _title: 'Salutation configuration'
  requirements:
    _permission: 'administer site configuration'

Most of this route definition is the same as we saw earlier. There is one change, though, in that it maps to a form instead of a Controller. This means that the entire page is a form page. Also, since the path is within the administration space, it will use the administration theme of the site. What is left to do now is to create our form class inside the /Form folder of our namespace (a standard practice directory for storing forms, but not mandatory).

Due to the power of inheritance, our form is actually very simple. However, I will explain what goes on in the background and guide you on your path to building more complex forms. So, here we have our form:

namespace Drupal\hello_world\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
 * Configuration form definition for the salutation
        message.
 */
class SalutationConfigurationForm extends ConfigFormBase {
  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['hello_world.custom_salutation'];
  }
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'salutation_configuration_form';
  }
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface
    $form_state) {
    $config = $this->config('hello_world.custom
        _salutation');
    $form['salutation'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Salutation'),
      '#description' => $this->t('Please provide the
          salutation you want to use.'),
      '#default_value' => $config->get('salutation'),
    ];
    return parent::buildForm($form, $form_state);
  }
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form,
    FormStateInterface $form_state) {
    $this->config('hello_world.custom_salutation')
      ->set('salutation', $form_state->
         getValue('salutation'))
      ->save();
    parent::submitForm($form, $form_state);
  }
}

Clearing the cache and navigating to admin/config/salutation-configuration will present you with your simple configuration form via which you can save a custom salutation message:

Figure 2.3: Salutation configuration form

Figure 2.3: Salutation configuration form

Note

To access this page, you need to be logged in as the administrator user, who typically has the permission we defined for this route: administer site configuration.

Later, we will make use of that value. However, first, let’s talk a bit about forms in general, and then this form in particular.

A form in Drupal is represented by a class that implements FormInterface. Typically, we either extend from FormBase or ConfigFormBase, depending on what its purpose is. In this case, we created a configuration form, so we extended it from the latter class.

There are four main methods that come into play in this interface:

  • getFormId(): Returns a unique, machine-readable name for the form.
  • buildForm(): Returns the form definition (an array of form element definitions and some extra metadata, as needed).
  • validateForm(): The handler that gets called to validate the form submission. It receives the form definition and a FormStateInterface object that contains, among other things, the submitted values. You can flag invalid values on their respective form elements, which means that the form is not submitted but refreshed (with the offending elements highlighted).
  • submitForm(): The handler that gets called when the form is submitted (if validation has passed without errors). It receives the same arguments as validateForm(). You can perform operations such as saving the submitted values or triggering some other kind of flow.

Defining a form, in a nutshell, means creating an array of form element definitions. The resulting form is very similar to the render array we mentioned earlier. When creating your forms, you have many form element types to use. A complete reference of what they are and what their options are (their definition specificities) can be found on the Drupal Form API reference page (https://api.drupal.org/api/drupal/elements/10.0.x).

From a dependency injection point of view, forms can receive arguments from the service container in the same way that we injected the salutation service into our Controller. As a matter of fact, ConfigFormBase, which we are extending in our example, injects the config.factory service because it needs to use it for reading and storing configuration values. Drupal is full of these helpful classes that we can extend and that provide a bunch of useful boilerplate code that is very commonly used across the Drupal ecosystem.

If the form you are building is not storing or working with configuration, you will typically extend from FormBase, which provides some static methods and traits, as well as implements some interfaces. The same word of caution goes for using its helper service methods as it went for the ones in ControllerBase: if you need services, you should always inject them.

Let’s turn to our form class and dissect it a bit now that we know a thing or two about forms.

We have the getFormId() method. Check. We also have buildForm() and submitForm(), but not validateForm(). The latter is not mandatory, and we don’t need it for our example, but if we did, we could have something like this:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form,
    FormStateInterface $form_state) {
  $salutation = $form_state->getValue('salutation');
  if (mb_strlen($salutation) > 20) {
    $form_state->setErrorByName('salutation', $this->
        t('This salutation is too long'));
  }
}

In this validation handler, we basically check whether the submitted value for the salutation element is longer than 20 characters. If so, we set an error on that element (to turn it red usually) and specify an error message on the form state specific to this error. The form will then be refreshed (with the error presented) and the submit handler will not be called.

For the purposes of our example, this is, however, not necessary, so I will not include it in the final code.

Note

Form validation error messages, by default, are printed at the top of the page. However, with the core Inline Form Errors module, we can have the form errors printed right beneath the actual elements. This is much better for accessibility, as well as for clarity when dealing with large forms. Note that the standard Drupal 10 installation doesn’t have this module enabled, so you’ll have to enable it yourself if you want to use it.

If we turn back to our form class, we also see a strange getEditableConfigNames() method. This is required by the ConfigFormBaseTrait, which is used in the ConfigFormBase class that we are extending. It needs to return an array of configuration object names that this form intends to edit. This is because there are two ways of loading configuration objects: for editing and for reading (immutable). With this method, we inform it that we want to edit that configuration item.

As we see on the first line of buildForm(), we are using the config() method of the previously mentioned trait to load up our editable configuration object from the Drupal configuration factory. This is to check the value that is currently stored in it. Then, we define our form elements (in our case, one—a simple text field). For #default_value (the value present in the element when the user goes to the form), we put whatever is in the configuration object. The rest of the element options are self-explanatory and pretty standard across all element types. Consult the Form API reference to see what other options are available and for which element types. Finally, at the end of the method, we also call the parent method because that provides the form’s submit button, which for our purposes is enough.

The last method we need is the submit handler, which loads up the editable configuration object, puts the submitted value in it, and then saves it. Finally, it also calls the parent method, which then simply sends a success message to the user on the screen using the Messenger service—a standard way of showing the user a success or error message.

That is pretty much it; this will work just fine.

From the point of view of configuration, we used ConfigFormBase to make our lives easier and combine the form aspect with that of the configuration storage. In a later chapter, we will talk more about the different types of storage and talk about how to work with configuration objects. So, no worries if you are left a bit unclear about how configuration works.

Note

As I mentioned in this book’s introduction, learning by reading code is important. So, to learn about all the existing Drupal form elements, check out their individual classes that implement Drupal\Core\Render\Element\FormElementInterface where you can also see code comments as to how they should be used.

Altering forms

Before going ahead with our proposed functionality, I would like to open a parenthesis and discuss forms in a bit more detail. An important thing that you will do as a module developer is alter forms defined by other modules or Drupal core. So, it behooves us to talk about it early on and what better moment than now, when defining the form itself is still fresh in our minds?

Obviously, the form we just created belongs to us and we can change it however we want. However, many forms out there have been defined by other modules and there will be just as many times that you will want to make changes to them. Drupal provides us with a very flexible, albeit still procedural, way of doing so—a suite of alter hooks; but what are alter hooks?

The first thing we did in this chapter was implement hook_help(). That is an example of an invoked hook by which a caller (Drupal core or any module) asks other modules to provide input. This input is then aggregated in some way and made use of. The other type of hooks we have in Drupal is the alter hooks, which are used to allow other modules to make changes to an array or an object before that array or object is used for whatever it is used for. So, in the case of forms, there are some alter hooks that allow modules to make changes to the form definition before it’s processed for rendering.

You may be wondering why I am saying that to make changes to a form, we have more than one alter hook. Let me explain by giving an example of how other modules could alter the form we just defined (this will not be included in our code base):

/**
 * Implements hook_form_alter().
 */
function my_module_form_alter(&$form, \Drupal\Core\
    Form\FormStateInterface $form_state, $form_id) {
  if ($form_id === 'salutation_configuration_form') {
    // Perform alterations.
  }
}

In the code above, we implement the generic hook_form_alter(), which gets fired for all forms when being built, and we do so inside a module called my_module. The first two arguments are the form and form state (the same as we saw in the form definition), the former being passed by reference. This is the typical alter concept—we make changes to an existing variable and don’t return anything. The third parameter is the form ID, the one we defined in the getFormId() method of our form class. We check to ensure that the form is correct and then we can make alterations to the form.

This is, however, almost always the wrong approach, because the hook is fired for all forms indiscriminately. Even if we don’t do anything for most of them, it’s still a useless function call, not to mention that if we want to alter 10 forms in our module, there will be a lot of if conditionals in there—the price we pay for procedural functions. Instead, though, we can do this:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function my_module_form_salutation_configuration_form
    alter(&$form, \Drupal\Core\Form\FormStateInterface
        $form_state, $form_id) {
  // Perform alterations.
}

Here, we are implementing hook_form_FORM_ID_alter(), which is a dynamic alter hook in that its name contains the actual ID of the form we want to alter. So, with this approach, we ensure that this function is called only when it’s time to alter OUR form (or a specific form). The other benefit is that if we need to alter another form, we can implement the same kind of hook for that and have our logic neatly separated.

Custom submit handlers

So, up to this point, we have seen how other modules can make changes to our form. That means adding new form elements, changing existing ones, and so on. But what about our validation and submit handlers (those methods that get called when the form is submitted)? How can those be altered?

Typically, for the forms that we defined as we did, it’s pretty simple. Once we alter the form and inspect the $form array, we can find a #submit key, which is an array that has one item: ::submitForm. This is simply the submitForm() method on the form class. So, what we can do is either remove this item and add our own function or simply add another item to that array:

/**
 * Implements hook_form_FORM_ID_alter().
 */
function my_module_form_salutation_configuration_form
    alter(&$form, \Drupal\Core\Form\FormStateInterface
        $form_state, $form_id) {
  // Perform alterations.
  $form['#submit'][] = 'hello_world_salutation_
    configuration_form_submit';
}

And the callback we added to the #submit array above can look like this:

/**
 * Custom submit handler for the form_salutation_
      configuration form.
 *
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
function my_module_salutation_configuration_form_submit
     (&$form, \Drupal\Core\Form\FormStateInterface
        $form_state) {
  // Do something when the form is submitted.
}

So, the cool thing is that you can choose to tack on your own callback or replace the existing one. Keep in mind that the order they are in that array is the order in which they get executed. So, you can also change the order if you want.

There is another case though. If the submit button on the form has a #submit property specifying its own handler, the default form #submit handlers we saw just now won’t fire anymore. This was not the case with our form. So, in that situation, you will need to add your own handler to that form’s submit element array instead.

Finally, when it comes to the validation handler, it works the same as with the submit handler, but it all happens under the #validate array key.

Feel free to experiment with altering existing forms and inspecting the variables they receive as arguments.

Rendering forms

Staying on forms for just a bit longer, let’s quickly learn how to render forms programmatically. We have already seen how to map a form to a route definition so that the page being built contains the form when accessing the route path. However, there are times when we need to render a form programmatically, either inside a Controller or a block, or wherever we want. We can do this using the FormBuilder service.

The form builder can be injected using the form_builder service key or used statically via the shorthand:

$builder = \Drupal::formBuilder();

Once we have it, we can build a form, like so:

$form = $builder->getForm('Drupal\hello_world\Form\
    SalutationConfigurationForm');

In the code above, $form will be a render array of the form that we can return, for example, inside a Controller. We’ll talk more about render arrays a bit later, and you’ll understand how they get turned into actual form markup. However, for now, this is all you need to know about rendering forms programmatically—you get the form builder and request from it the form using the fully qualified name of the form class.

With this, we can close the parenthesis on forms.

Service dependencies

In the previous section, we created a form that allows administrators to set up a custom salutation message to be shown on the page. This message is stored in a configuration object that we can now load in our HelloWorldSalutation service. So, let’s do just that with a two-step process.

First, we will need to change our service definition to give our service an argument—the configuration factory (the service responsible for loading config objects). This is how our service definition should look now:

hello_world.salutation:
  class: Drupal\hello_world\HelloWorldSalutation
  arguments: ['@config.factory']

The addition is the arguments key, which is an array of service names proceeded by @. In this case, config.factory is the responsible service name, which, if we check in the core.services.yml file, we can note that it maps to the Drupal\Core\Config\ConfigFactory class.

So, with this change, the HelloWorldSalutation class will receive an instance of ConfigFactory. All we need to do now is adjust our class to actually receive it:

/**
 * @var \Drupal\Core\Config\ConfigFactoryInterface
 */
protected $configFactory;
/**
 * HelloWorldSalutation constructor.
 *
 * @param \Drupal\Core\Config\ConfigFactoryInterface
      $config_factory
 */
public function __construct(ConfigFactoryInterface
    $config_factory) {
  $this->configFactory = $config_factory;
}

There’s nothing too complicated going on here. We added a constructor and set the config factory service on a property. We can now use it to load our configuration object that we saved in the form. However, before we do that, we should also use the ConfigFactoryInterface class at the top of the file:

use Drupal\Core\Config\ConfigFactoryInterface;

Now, at the top of the getSalutation() method, we can add the following bit:

$config = $this->configFactory->get
     ('hello_world.custom_salutation');
$salutation = $config->get('salutation');
if ($salutation !== "" && $salutation) {
  return $salutation;
}

With this addition, we are loading the configuration object we saved in the form, and from it, we request the salutation key, where, if you remember, we stored our message. If there is a value in there, we will return it. Otherwise, the code will continue, and our previous logic of time-based greeting will apply.

So now, if we reload our initial page, the message we saved through the form should show up. If we then return to the form and remove the message, this page should default back to the original dynamic greeting. Neat, right? Don’t forget to clear the caches before you check this because we changed our service definition and that requires a cache clear.

Note

If you are not seeing the changes on the page after editing/removing the salutation message, ensure you have the caches on your local site disabled as we have not yet taken that into account so early in our journey.

Let’s now look at how we can create a custom block that we can place anywhere we like and that will output the same thing as our page.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Explore the new features and improved capabilities of Drupal 10 core
  • Discover Drupal APIs and elevate your proficiency by leveraging PHP coding
  • Implement efficient data management and data security by creating dedicated modules

Description

Embark on a journey of Drupal module development with the latest edition of this must-have guide written by Daniel Sipos – a Drupal community member! This fourth edition is meticulously revised to cover the latest Drupal 10 enhancements that will help you build custom Drupal modules with an understanding of code deprecations, changing architecture, data modeling, multilingual ecosystem, and so on. You’ll begin with understanding the core components of Drupal 10 architecture, discovering its subsystems and unlocking the secrets of creating your first Drupal module. Further, you'll delve into Drupal logging and mailing systems, creating theme hooks, and rendering a layout. As you progress, you'll work with different types of data storage, custom entities, field types, and work with Database APIs for lower-level database queries. You'll learn to reap the power of JavaScript and ensure that your code works seamlessly on multilingual sites. You'll also learn to create custom views, automate tests for your functionalities, and write secure code for your Drupal apps. By the end of this book, you'll have gained confidence in developing complex modules that can solve even the most complex business problems and might even become a valuable contributor to the Drupal community!

Who is this book for?

If you are a Drupal developer looking to create custom modules for Drupal sites and cater to business needs, this book is your one-stop solution. Drupal 10 Module Development will be helpful for Drupal site builders and PHP developers with basic object-oriented programming skills, looking to upskill themselves in Drupal module development. A basic working experience with Symfony will be helpful but not mandatory.

What you will learn

  • Gain insight into the Drupal 10 architecture for developing advanced modules
  • Master different Drupal 10 subsystems and APIs
  • Optimize data management by modeling, storing, manipulating, and processing data efficiently
  • Present data and content cleanly and securely using the theme system
  • Understand helpful functions while dealing with managed and unmanaged files
  • Ensure your Drupal app has business logic integrity with automated testing
  • Implement secure coding in Drupal

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Apr 21, 2023
Length: 580 pages
Edition : 4th
Language : English
ISBN-13 : 9781837631803
Languages :
Concepts :
Tools :

What do you get with a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing

Product Details

Publication date : Apr 21, 2023
Length: 580 pages
Edition : 4th
Language : English
ISBN-13 : 9781837631803
Languages :
Concepts :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 134.97
Modernizing Drupal 10 Theme Development
$39.99
Drupal 10 Development Cookbook
$49.99
Drupal 10 Module Development
$44.99
Total $ 134.97 Stars icon

Table of Contents

20 Chapters
Chapter 1: Developing for Drupal Chevron down icon Chevron up icon
Chapter 2: Creating Your First Module Chevron down icon Chevron up icon
Chapter 3: Logging and Mailing Chevron down icon Chevron up icon
Chapter 4: Theming Chevron down icon Chevron up icon
Chapter 5: Menus and Menu Links Chevron down icon Chevron up icon
Chapter 6: Data Modeling and Storage Chevron down icon Chevron up icon
Chapter 7: Your Own Custom Entity and Plugin Types Chevron down icon Chevron up icon
Chapter 8: The Database API Chevron down icon Chevron up icon
Chapter 9: Custom Fields Chevron down icon Chevron up icon
Chapter 10: Access Control Chevron down icon Chevron up icon
Chapter 11: Caching Chevron down icon Chevron up icon
Chapter 12: JavaScript and the Ajax API Chevron down icon Chevron up icon
Chapter 13: Internationalization and Languages Chevron down icon Chevron up icon
Chapter 14: Batches, Queues, and Cron Chevron down icon Chevron up icon
Chapter 15: Views Chevron down icon Chevron up icon
Chapter 16: Working with Files and Images Chevron down icon Chevron up icon
Chapter 17: Automated Testing Chevron down icon Chevron up icon
Chapter 18: Drupal Security Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.7
(10 Ratings)
5 star 70%
4 star 30%
3 star 0%
2 star 0%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




b damron May 24, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I've been doing Drupal since Drupal 6 - stumbling my way into figuring things out. I've worked with 7,8 and 9 and consider myself a pretty good Drupal dev.But I really didn't understand what I was doing (just knew what needed to be done) until I read this book. Daniel explains a lot about Object Oriented Programming while explaining how Drupal 10 works and how to build out any custom functionality you may need. I may actually be able to pass an Acquia exam at this point.
Amazon Verified review Amazon
Gretel Gutierrez Jul 06, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Having worked with Drupal since version 7, I have consistently relied on their books for guidance, and this one is no exception. The section dedicated to Symfony Event Dispatcher is particularly noteworthy, providing practical insights for seamless implementation. Furthermore, the book caters to developers of all levels, presenting beginner-friendly explanations of key concepts like render arrays. Whether you specialize in back-end or front-end development, this book is an essential addition to your library, offering comprehensive coverage and expert advice.
Amazon Verified review Amazon
doug vann Jun 04, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Stop trying to piece together dozens of free online learning resources across multiple publishers and buy this book! After that, download the free accompanying code from github and enjoy this very methodic and highly comprehensive approach to either start or expand your Drupal Module development skills!This book is truly a one-stop-shop to either get started or greatly expand your skills. Book author, Danial Sipos, has taken great care to keep a consistent voice and teaching method throughout the very deep dive into Drupal Module development.This book doesn't shy away from the fact that creating a custom module often requires that you juggle multiple concepts at once. As the book methodically walks you through these steps, Daniel is able to dive deep into the individual specifics of APIs, functions, & files while effortlessly keeping you aware of why you are doing each step and what it means to the overall goal.Every function & every file has a purpose; this book does a fantastic job of making sure that you not only have code that works, but that you understand how and why the code works.Seriously... I am so thankful that this digital age still includes quality, long-form publications that aren't afraid to tackle sophisticated concepts and keep them within reach of anyone willing to read and follow along!
Amazon Verified review Amazon
Luciano Nicacio Jul 31, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Excellent book, straight to the point! I gained a good base on Drupal module development through this book. I've even been using some snippets as a starter point for my custom modules, it's definitely helping me progress in my career as a developer.
Amazon Verified review Amazon
M. Padiernos Jul 28, 2023
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Since the announcement of Drupal 10, ways to ascertain the difference between Drupal 10 and the previous versions have steadfastly increased but, along with this book, a developer can leap ahead of the curve! The book gives clear and thorough insights in module development with examples and hands-on application with projects you can do at your pace. It was a joy to go through and the book will definitely be indispensable in any developer's toolbox.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is included in a Packt subscription? Chevron down icon Chevron up icon

A subscription provides you with full access to view all Packt and licnesed content online, this includes exclusive access to Early Access titles. Depending on the tier chosen you can also earn credits and discounts to use for owning content

How can I cancel my subscription? Chevron down icon Chevron up icon

To cancel your subscription with us simply go to the account page - found in the top right of the page or at https://subscription.packtpub.com/my-account/subscription - From here you will see the ‘cancel subscription’ button in the grey box with your subscription information in.

What are credits? Chevron down icon Chevron up icon

Credits can be earned from reading 40 section of any title within the payment cycle - a month starting from the day of subscription payment. You also earn a Credit every month if you subscribe to our annual or 18 month plans. Credits can be used to buy books DRM free, the same way that you would pay for a book. Your credits can be found in the subscription homepage - subscription.packtpub.com - clicking on ‘the my’ library dropdown and selecting ‘credits’.

What happens if an Early Access Course is cancelled? Chevron down icon Chevron up icon

Projects are rarely cancelled, but sometimes it's unavoidable. If an Early Access course is cancelled or excessively delayed, you can exchange your purchase for another course. For further details, please contact us here.

Where can I send feedback about an Early Access title? Chevron down icon Chevron up icon

If you have any feedback about the product you're reading, or Early Access in general, then please fill out a contact form here and we'll make sure the feedback gets to the right team. 

Can I download the code files for Early Access titles? Chevron down icon Chevron up icon

We try to ensure that all books in Early Access have code available to use, download, and fork on GitHub. This helps us be more agile in the development of the book, and helps keep the often changing code base of new versions and new technologies as up to date as possible. Unfortunately, however, there will be rare cases when it is not possible for us to have downloadable code samples available until publication.

When we publish the book, the code files will also be available to download from the Packt website.

How accurate is the publication date? Chevron down icon Chevron up icon

The publication date is as accurate as we can be at any point in the project. Unfortunately, delays can happen. Often those delays are out of our control, such as changes to the technology code base or delays in the tech release. We do our best to give you an accurate estimate of the publication date at any given time, and as more chapters are delivered, the more accurate the delivery date will become.

How will I know when new chapters are ready? Chevron down icon Chevron up icon

We'll let you know every time there has been an update to a course that you've bought in Early Access. You'll get an email to let you know there has been a new chapter, or a change to a previous chapter. The new chapters are automatically added to your account, so you can also check back there any time you're ready and download or read them online.

I am a Packt subscriber, do I get Early Access? Chevron down icon Chevron up icon

Yes, all Early Access content is fully available through your subscription. You will need to have a paid for or active trial subscription in order to access all titles.

How is Early Access delivered? Chevron down icon Chevron up icon

Early Access is currently only available as a PDF or through our online reader. As we make changes or add new chapters, the files in your Packt account will be updated so you can download them again or view them online immediately.

How do I buy Early Access content? Chevron down icon Chevron up icon

Early Access is a way of us getting our content to you quicker, but the method of buying the Early Access course is still the same. Just find the course you want to buy, go through the check-out steps, and you’ll get a confirmation email from us with information and a link to the relevant Early Access courses.

What is Early Access? Chevron down icon Chevron up icon

Keeping up to date with the latest technology is difficult; new versions, new frameworks, new techniques. This feature gives you a head-start to our content, as it's being created. With Early Access you'll receive each chapter as it's written, and get regular updates throughout the product's development, as well as the final course as soon as it's ready.We created Early Access as a means of giving you the information you need, as soon as it's available. As we go through the process of developing a course, 99% of it can be ready but we can't publish until that last 1% falls in to place. Early Access helps to unlock the potential of our content early, to help you start your learning when you need it most. You not only get access to every chapter as it's delivered, edited, and updated, but you'll also get the finalized, DRM-free product to download in any format you want when it's published. As a member of Packt, you'll also be eligible for our exclusive offers, including a free course every day, and discounts on new and popular titles.