Inzicht in Drupal 8 - Deel 2: de service container

Deel 2: de Service Container

Dit is het tweede deel van het 'Inzicht in Drupal 8' artikel. In het eerste deel hebben we de algemene structuur van Drupal 8 gezien en hoe die gerelateerd is aan Symfony. Symfony (en Drupal 8) bestaat uit meerdere componenten. In dit artikel informeren we je over de service container en hoe die wordt gebruikt in Drupal 8. Het is heel belangrijk om hier meer over te weten voordat je in routing verdiept.

Symfony gebruikt een service container die kan worden ingezet om services in de applicatie efficiënt te beheren. Dit concept staat ook bekend als Dependency Injection.

Deze Service Container is een algemeen object dat door de Kernel wordt gecreëerd en opgenomen voordat een verzoek wordt behandeld. Het kan later in code gebruikt worden om diensten op te halen, lazy loaded on the fly. Services zijn algemene objecten die gebruikt kunnen worden om specifieke taken te vervullen, zoals een Mailer-service of een database-connector. Een service correspondeert met precies één class. De service container is erg belangrijk omdat het de beschikbare services bevat, hun relaties en configuraties kent, en deze zelfs bouwt!

Afhankelijkheden en arguments

Een dienst kan afhankelijk zijn van andere diensten. De Symfony-documentatie gebruikt het voorbeeld van een NewsletterManager-service die een Mailer-service nodig heeft om de e-mails daadwerkelijk te verzenden. Deze afhankelijkheden worden ook beheerd in de Service Container. Bij het creëren van een service worden zijn afhankelijkheden geleverd via arguments in de class constructor. Interfaces worden gebruikt om te bepalen welke methoden de afhankelijke services moeten leveren, zodat de service implementatie kan worden vervangen door een andere als dat nodig mocht zijn.

Configuratie

De diensten in de service container kunnen op verschillende manieren worden geconfigureerd: via code (bekend als Extensies), via XML, via YAML, enz. Symfony maakt gebruik van een combinatie van deze, maar laat het grootste deel van de core service configuratie over aan de Code Bundle code. De configuratie van Drupal is anders en meestal gebaseerd op YAML-bestanden. Het gebruikt services.core.yml voor alles dat core gerelateerd is. Deze configuratie kan uitgebreid worden door modules en ServiceProvider classes. Dit laatste is vergelijkbaar met Symfony's Extensions. Het laden van deze configuraties wordt afgehandeld door de Kernel.

YAML biedt een leesbare en flexibele syntaxis. Een voorbeeld in Drupal 8  (uit 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']]
  ...

Hier wordt router_listener service gedefinieerd. De corresponderende class is nodig om de listener te kunnen laden. De arguments bepalen netjes dat dat het eerste argument van de RouterListener constructor (de url of request matcher)  '@router' moet zijn, die de service definieert met de service id 'router'. Deze router is ook gedefinieerd in de configuratie en heeft een andere 'class' dan in Symfony, zoals eerder uitgelegd.

Tagged services

Tags worden gebruikt om bepaalde 'tagged' diensten te laden. In de praktijk worden deze soms op een 'hooky' manier in Drupal gebruikt. In het voorbeeld hieronder (node.services.yml) voegt de node module een toegangs check toe voor de 'add node' pagina die is gebaseerd op het  access_check services patroon, dat wordt gebruikt na routing om te checken of toegang moet worden verleend:

services:
  ...

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

Compiler passes

Hoe begrijpt Drupal deze tags en hoe weet het wat ermee te doen? Nou, Symfony heeft nog een andere methode om de service container dynamisch te configureren: compiler passes. De service container wordt direct samengesteld na het opbouwen van de statische configuratie. Op meerdere momenten gedurende deze fase maakt deze service container het mogelijk dat objecten, die de CompilerPassInterface implementeren, de configuratie wijzigen. In Drupal registreert de CoreServiceProvider enkele belangrijke compiler passes, zoals de  RegisterAccessChecksPass, die probeert alle services die met  access_check zijn getagd te vinden (zie vorige voorbeeld) en voegt deze toe aan  AccessManager (m.b.v. addMethodCall container service directive). Als in de routingsfase de  AccessManager wordt gevraagd om toegang te controleren, zal het onder andere de NodeAddAccessCheck controleren. Er zijn verschillende andere compiler passes in de Drupal core, zoals die om routeparameters te converteren naar objecten en strings te vertalen. In de praktijk zal je deze tagged services soms in jouw module moeten gebruiken om custom access checks toe te voegen en path parameters te converteren!

Swapping services

De service container maakt flexibele service configuratie mogelijk. Drupal 8 maakt gebruik van service arguments om de wijze waarop Symfony functioneert voor haar eigen doeleinden, te wijzigen. Drupal heeft bijvoorbeeld een ander url routingmechanisme nodig dan Symfony. Dat  doet het door te voorzien in een alternatieve 'router'-service (Symfony\Cmf\Component\Routing\ChainRouter) compared to the one used by Symfony (Symfony\Bundle\FrameworkBundle\Routing\Router). Dit wordt bereikt zonder dat er enige coderegel in de  router_listener service (Symfony\Component\HttpKernel\EventListener\RouterListener) aangepast hoeft te worden!  Dit is mogelijk omdat beide routers de  RequestMatcherInterface, implementeren, wat de enige vereiste is voor het eerste argument van de  RouterListener.

Conclusie

In dit deel zijn we ingegaan op het Service Container-onderdeel in Drupal 8. Je hebt nu een beter inzicht in hoe Drupal 8 zijn eigen diensten injecteert in plaats van Symfony2 zonder dat je een regel code hoeft te veranderen. Hierdoor kan Symfony2 eenvoudig door de Drupal core maintainers worden bijgewerkt. Als je Drupal 8 bestudeerd zal je vaak het  core.services.yml bestand moeten raadplegen om erachter te komen welke diensten worden gebruikt.

In deel 3 van dit artikel gaan we nader in op de algemene flow of control in Drupal 8 en de routing.

Interesse? 

Delen