Understanding Drupal 8, part 2: The Service Container

Part 2: The Service Container

This is the second part of the ‘Understanding Drupal 8’ article. In the first part, we have seen the general structure of Drupal 8 and how it relates to Symfony. Symfony (and Drupal 8) consists out of several components. In this article you will learn about the service container and how it is used in Drupal 8. It is very important to know about it before learning about routing.

Symfony uses a service container that can be used to efficiently manage services in the application. This concept is also known as Dependency Injection.

This Service Container is a global object that is created and contained by the Kernel before a request is handled. It can be used later in code to fetch services, lazy-loaded on the fly. Services are global objects that can be used to fulfil specific tasks, such as a Mailer service, or a database connector. A service corresponds to exactly one class. The service container is very important as it contains the available services, knows about their relations and configurations, and even constructs them!

Dependencies and arguments

A service may depend on other services. The Symfony documentation uses the example of a NewsletterManager service that needs a Mailer service to actually send the emails. These dependencies are also managed in the Service Container. When creating a service, his dependencies are supplied via arguments in the class constructor. Interfaces are used to define which methods the dependent services should provide, so that the service implementation can be swapped with another one when necessary.

Configuration

The services in the service container can be configured in several ways: via code (known as Extensions), via XML, via YAML, etc. Symfony uses a combination of these but leaves most of the core service configuration to the Framework Bundle's code. Drupal's service configuration is different, and mostly based on YAML-files. It uses services.core.yml for everything core-related. This configuration can be extended by modules and ServiceProvider classes. The latter is similar to Symfony's Extensions. The loading of these configurations is handled by the Kernel.

YAML provides a readable and flexible syntax. An example in Drupal 8 (taken from services.core.yml):

services:
  ...

  router_listener:
  class: Symfony\Component\HttpKernel\EventListener\RouterListener
  tags:
    - { name: event_subscriber }
  arguments: ['@router']
  ...
  router:
    class: Symfony\Cmf\Component\Routing\ChainRouter
    calls:
      - [setContext, ['@router.request_context']]
      - [add, ['@router.dynamic']]
  ...

Here the router_listener service is defined. The corresponding class is required to be able to load the listener. The arguments property defines that the first argument of the RouterListener constructor (the url or request matcher) should be '@router', which defines the service with the service id 'router'. This router is also defined in the configuration, and has a different 'class' than used in Symfony, as I have just explained.

Tagged services

Tags are used to load certain 'tagged' services. In practice, these are sometimes used in a 'hooky' way in Drupal. In the example below (node.services.yml) the node module adds an access checker for the 'add node' page that is based on the access_check services pattern, which is used after routing to find out if access should be granted:

services:
  ...

  access_check.node.add:
    class: Drupal\node\Access\NodeAddAccessCheck
    arguments: ['@plugin.manager.entity']
    tags:
      - { name: access_check }
        ...

Compiler passes

How does Drupal understand these tags and know what to do with them? Well, Symfony has yet another method of dynamically configuring the service container: compiler passes. The service container is actually compiled directly after building it from the static configuration. At several points during this phase, it allows objects implementing the CompilerPassInterface to modify the configuration. In Drupal, the CoreServiceProvider registers some important compiler passes, such as the RegisterAccessChecksPass, which attempts to find all services tagged with access_check (see previous example) and adds it to the AccessManager (by using the addMethodCall container service directive). When, in the routing phase, the AccessManager service is asked to check for access, it will amongst others, check the NodeAddAccessCheck. There are several other compiler passes in the Drupal core, such as for converting route parameters to objects and translating strings. In practice you will sometimes need to use these tagged services in your module to add custom access checks and convert path parameters!

Swapping services

The service container makes flexible service configuration possible. Drupal 8 makes use of service arguments to alter the way that Symfony is functioning for its own purposes. For example, Drupal needs a different url routing mechanism than Symfony. It does so by providing an alternative 'router' service (Symfony\Cmf\Component\Routing\ChainRouter) compared to the one used by Symfony (Symfony\Bundle\FrameworkBundle\Routing\Router). This can be done without having to change any line of code in the router_listener service (Symfony\Component\HttpKernel\EventListener\RouterListener) itself! This is possible because both routers implement the RequestMatcherInterface, which is the one requirement for the RouterListener's first argument.

Conclusion

In this part, we have learned about the Service Container component in Drupal 8. You now have a better understanding on how Drupal 8 injects its own services instead of Symfony2 without having to change a line of code. This allows Symfony2 to be updated easily by the Drupal core maintainers. When learning about Drupal 8, you will often have to consult the core.services.yml file to find out which services are being used.

In the next part of this article we’ll have a detailed look at the general flow of control in Drupal 8 and routing.

Other parts

Part 1: The Structure of Drupal 8
Part 2: Service Container
Part 3: Routing
Part 4: Plugins and Entities

Click here for more info on Cipix Internet Agency.

 

Interesse? 

Delen