my profile picture

Hey, I'm Achilles!!!

I'm a web developer and a technology enthusiast. I write code for a living, mostly in PHP and JavaScript.

Thessaloniki - Greece
Joined on December 1990

Service Container For Drupal Geeks

drupal8 banner

Drupal8 and Symfony have a great relationship, that's not news. In the context of this relationship there are some cool stuff Drupal is getting from Symfony and I am really happy about that!!! One of my favorite additions to Drupal is the concept of Service Container also known as Dependency Injection Container brought to Drupal from Symfony framework and more specifically from Symfony's Dependency Injection component.

To understand what the Service Container is, first we need to understand the term Service, get used to this word, you will hear it a lot. Generally speaking services are useful PHP objects that are used globally in you application and each of them provides a specific functionality that we usually want to reuse(e.g. delivering emails). Here is a good explanation.

Now, Service Container(the actual class name is Container) is a global object that manages the instantiation of services. Think of it as a Container with many services inside, in fact the Container holds every service Drupal uses. Through the Container you have access to all of them and the best part is that when a service is not used, the Container does not create that object, this saves memory and increases the speed of your application.

Drupal is generous and provides you with a ton of services from the core. Contrib modules can register their services and you can even create your own, that's super cool, I will get there in awhile!!!

Discovering existing services

I found two cool ways to see what's in the Container:

  1. The first way is to use the Devel module, which provides you with the Web Profiler, a sweet debugging bar (like Symfony's ). You must enable Services tab, It's not enabled by default. By clicking the Services tab it will show the list of services and some useful information, it even shows you whether or not a service is initialized.

devel profiler bar

  1. The second cool way is by using a project called Drupal Console it's a CLI tool to generate boilerplate code, interact and debug Drupal 8.

Run:

drupal container:debug

And you will see a list of services ready to be used. That's a good way to check if your own services are registered properly. In the first column you can see the 'service machine name' and in the second column the 'service class'. That information is useful and we will need it in the future.

list of services in the container

You can just check the core.services.yml file or every modulename.services.yml file but that's unproductive.

Accessing a service through the container

For most cases you can grab the container with a method called Dependency Injection. I will demonstrate Dependency Injection for the controllers but the process is similar for other classes, so if you have any trouble just check the docs.

The first thing you will probably need is to extend the ControllerBase class from your controller:

namespace Drupal\myexample\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Class DefaultController.
 *
 * @package Drupal\myexample\Controller
 */
class DefaultController extends ControllerBase {
}

ControllerBase is base class for controllers and it gives us access to a number of utility methods and to the Container itself but most important it gives us the create method which is a factory method that returns a new instance of our class and passes any needed dependencies(services) to the constructor.

public static function create(ContainerInterface $container)
{
   //here I get the 'entity_type.manager' service from the container
   $entityTypeManager = $container->get('entity_type.manager');

   return new static($entityTypeManager);
}

Next you need to implement the actual Dependency Injection, here I will use Constructor injection but be aware that there are other ways too.

use Drupal\Core\Entity\EntityTypeManagerInterface;

//..

private $entityTypeManger;

public function __construct(EntityTypeManagerInterface $entityTypeManager)
{
  $this->entityTypeManger = $entityTypeManager;
}

Now I can just use the service inside of my controller.

public function index() {
   // I have a custom entity type 'my_super_entity'
   $storage = $this->entityTypeManger->getStorage('my_super_entity');

   $entity = $storage->create();
   $entity->setName('AchillesKal');
   $entity->custom_age = '25';
   $entity->save();

}

There is also a global \Drupal class that can be used to retrieve services from the container but it's not the recommended procedure and should be only used in places where dependency injection is not an option.

// Retrieve the service object for machine name 'foo.bar'.
$foobar = \Drupal::service('foo.bar');

Defining a service

To define a service you must create a modulename.services.yml in the root directory of your module and the actual Class that will be the service we are trying to define in the container, in my case it's MySuperService

services:
  module_name.super_service:
    class: Drupal\module_name\Utils\MySuperService

We define a service machine name 'module_name.super_service' and in the class key we define the namespace of the class we want to pass to the container, that's it !!! Run:

drupal container:debug

And you should see your service.

But what if you want to have a service inside your service class??? Relax, that's an easy one, just add an arguments key to your service definition and you can pass any service you want.

services:
  module_name.super_service:
    class: Drupal\module_name\Utils\MySuperService
    arguments: ['@logger.factory']

Now you just have to inject it through the constructor:

private $loggerFactory;

public function __construct(LoggerChannelFactory $loggerFactory)
{
  $this->loggerFactory = $loggerFactory;
}

That's it, you are ready to DROCK!!!

Resources: 

https://api.drupal.org/api/drupal/core%21core.api.php/group/container/8

https://www.drupal.org/node/2133171

https://knpuniversity.com/screencast/drupal8-under-the-hood/what-is-the-service-container

http://symfony.com/doc/current/book/service_container.html

Comments

comments powered by Disqus