Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Drupal 10 Module Development

You're reading from   Drupal 10 Module Development Develop and deliver engaging and intuitive enterprise-level apps

Arrow left icon
Product type Paperback
Published in Apr 2023
Publisher Packt
ISBN-13 9781837631803
Length 580 pages
Edition 4th Edition
Languages
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Daniel Sipos Daniel Sipos
Author Profile Icon Daniel Sipos
Daniel Sipos
Arrow right icon
View More author details
Toc

Table of Contents (21) Chapters Close

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

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.

You have been reading a chapter from
Drupal 10 Module Development - Fourth Edition
Published in: Apr 2023
Publisher: Packt
ISBN-13: 9781837631803
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime