logo

Extra Block Types (EBT) - Nieuwe Layout Builder ervaring❗

Extra Block Types (EBT) - gestileerde, aanpasbare bloktypes: Slideshows, Tabs, Cards, Accordions en vele andere. Ingebouwde instellingen voor achtergrond, DOM Box, javascript-plugins. Ervaar vandaag al de toekomst van layout building.

Demo EBT-modules Download EBT-modules

❗Extra Paragraph Types (EPT) - Nieuwe Paragraphs ervaring

Extra Paragraph Types (EPT) - analoge op paragrafen gebaseerde set modules.

Demo EPT-modules Download EPT-modules

Scroll

12.15. Services en Dependency Injection.

29/09/2025, by Ivan

Menu

Wanneer we Drupal gebruiken en we moeten code van een contrib-module of een kernmodule gebruiken in een custom module, dan gebruiken we hook’s en services (diensten). We hebben al hooks gebruikt in dit artikel:

12.11.3. Hooks voor werken met Entity.

Laten we nu services bekijken. Een service is een PHP-object. Daarom, wanneer je een nieuwe PHP-class aanmaakt in je custom module, is het beter om die meteen in te richten als een service, zodat je code later op een standaard manier kan worden gebruikt in een andere module.

Drupal verzamelt alle services in het PHP-object Service Container, zodat Drupal informatie over alle beschikbare en gebruikte services op één plek bewaart. Je kunt dit object aanroepen en bekijken welke services je gebruikt:

<?php
$container = \Drupal::getContainer();
?>

https://api.drupal.org/api/drupal/core!lib!Drupal.php/function/Drupal%3A%3AgetContainer/9.2.x

Get container

Service container

Je kunt met dit object werken via de methodes has/get/set, maar meestal voegen we services toe aan de container met behulp van *.services.yml-bestanden in onze modules.

Laten we eens kijken naar de implementatie van de methode getContainer():

<?php
public static function getContainer() {
  if (static::$container === NULL) {
    throw new ContainerNotInitializedException('\\Drupal::$container is not initialized yet. \\Drupal::setContainer() must be called with a real container.');
  }
  return static::$container;
}
?>

De variabele van de Service Container is gedefinieerd als statisch, wat betekent dat na het aanroepen van index.php en tot het einde van de request-verwerking we de waarde van deze variabele overal kunnen krijgen: dit kan een class of hook in een module zijn of zelfs een .theme-bestand van een theme.

Hoe gebruik je services in Drupal?

Laten we nu overgaan tot hoe we de Service Container in Drupal gebruiken. In het object $container worden service-objecten opgeslagen, wat het mogelijk maakt om alle nodige logica voor het aanmaken van een object in de constructor uit te voeren en ons een al gebruiksklaar object te geven in onze custom module. Stel dat we een SQL-query naar de database moeten schrijven, dan roepen we gewoon het object voor databasewerk op uit de Service Container en dit database-object gebruikt al de credentials uit ons settings.php-bestand en maakt verbinding met MySQL bij het uitvoeren van onze SQL-query:

$query = \Drupal::database()->select('node_field_data', 'n');
$query->addField('n', 'nid');
$query->condition('n.title', 'About Us');
$query->range(0, 1);
$nid = $query->execute()->fetchField();

Als je kijkt naar de implementatie van de methode database(), zie je dat we het service-object database uit de Service Container gebruiken:

https://api.drupal.org/api/drupal/core%21lib%21Drupal.php/function/Drupal%3A%3Adatabase/9.2.x

<?php
public static function database() {
  return static::getContainer()
    ->get('database');
}
?>

Op deze manier laden we alleen die classes voor onze code die we op dat moment nodig hebben. Daarvoor gebruiken we het centrale opslagobject van de Service Container.

Hoe voeg je een service toe aan de Service Container?

Wanneer we een bestand *.services.yml aanmaken, laadt Drupal de services uit deze bestanden en slaat de objecten ervan op in de Service Container.

https://api.drupal.org/api/drupal/core%21modules%21syslog%21syslog.services.yml/9.2.x

core/modules/syslog/syslog.services.yml:

services:
  logger.syslog:
    class: Drupal\syslog\Logger\SysLog
    arguments: ['@config.factory', '@logger.log_message_parser']
    tags:
      - { name: logger }

In de variabele $container kan je een service toevoegen via de methode set(), maar meestal wordt dit gebruikt om dependencies te mocken in tests:

https://www.drupal.org/docs/automated-testing/phpunit-in-drupal/mocking-entities-and-services-with-phpunit-and-mocks

Wat is Dependency Injection?

Als je Code Sniffer draait, krijg je een foutmelding dat Drupal::database() moet worden aangepast en dat je de database moet aanroepen in de constructor van de class waarin we het object uit de Service Container gebruiken. Wanneer je een object uit de Service Container aanroept in de constructor van een class heet dit Dependency Injection (DI), bijvoorbeeld:

<?php

namespace Drupal\wisenet_connect\Form;

use Drupal\Core\Database\Connection;

/**
 * Implements the WisenetConfigurationForm form controller.
 *
 * This example demonstrates a simple form with a singe text input element. We
 * extend FormBase which is the simplest form base class used in Drupal.
 *
 * @see \Drupal\Core\Form\FormBase
 */
class WisenetGetCourseForm extends FormBase {

  /**
   * Active database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;


  /**
   * Constructs a WisenetGetCourseForm object.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection to be used.
   */
  public function __construct(Connection $database) {
    $this->database = $database;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
    );
  }

 ...

  /**
   * Implements course save handler.
   *
   * Function for save course data in course content type.
   */
  public function saveCourse($courses) {
     ...
      $query = $this->database->select('node__field_course_wisenet_id', 'nc');
      $query->addField('n', 'nid');
      $query->join('node_field_data', 'n', 'nc.entity_id = n.nid');
      $query->condition('nc.field_course_wisenet_id_value', $course['CourseOfferId']);
      $query->range(0, 1);

      $nid = $query->execute()->fetchField();
     ...
  }

In dit voorbeeld is een databasequery nodig in een form, daarom hebben we een extra methode create() toegevoegd, die wordt gebruikt om een instantie van de class aan te maken. De methode create() kan in verschillende classes en interfaces voorkomen, maar hij heeft altijd als parameter de variabele $container van het type ContainerInterface. Als in de methode create() een object uit de Service Container wordt aangeroepen $container->get('myservice.name'), dan wordt het teruggegeven object doorgegeven aan de constructor __construct() als argument (in ons geval $container->get('database') en het argument Connection $database).

Hoe je precies objecten uit de Service Container in de constructor van een controller (Controller), block (Block), form (BaseForm), configuratieformulier (ConfigForm) of custom class/service moet aanroepen, zullen we in de volgende artikelen bekijken.

Nadat we hebben bekeken hoe je objecten uit de Service Container op de juiste manier aanroept en gebruikt, zullen we bekijken hoe je je eigen services aanmaakt.

Ook zullen we bekijken hoe je classes voor services kan overschrijven, zodat je in plaats van de class van een contrib-module een class uit een custom module gebruikt.

Waarom hebben we Service Container en Dependency Injection nodig?

We kunnen namespaces gebruiken en code uit modules direct importeren, objecten voor externe classes aanmaken waar we ze nodig hebben, zonder Service Container. Maar dit veroorzaakt problemen bij het updaten van code. Bijvoorbeeld: we moeten een class voor het versturen van e-mails vervangen en deze class wordt in 200 verschillende plaatsen aangeroepen. Voor het gemak van code-updates maken we een Service in plaats van direct bestanden te includen. Nu, wanneer we e-mails via smtp willen versturen in plaats van via PHP mail(), hoeven we alleen de class voor de service te wijzigen en niet in 200 plaatsen het pad naar een nieuwe class aan te passen.

Dependency Injection lost het probleem op van dubbele aanroep van een service in één class. We hoeven niet twee keer de Service Container aan te roepen als we de service in verschillende methodes van dezelfde class gebruiken. We slaan het service-object gewoon op in een property van onze class en gebruiken het service-object vervolgens via $this->serviceName.

Natuurlijk kunnen we het ook zonder Service Container en Dependency Injection stellen, maar deze patronen standaardiseren onze code en maken deze eenvoudiger en beter onderhoudbaar.

Waar kan ik de naam van een service zien?

In ons voorbeeld hebben we de service "database":

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container) {
  return new static(
    // Hier voegen we de servicenaam toe.
    $container->get('database'),
  );
}

maar als je een service toevoegt uit een contrib/custom module, dan kan die een naam hebben zoals:

module_name.service_name

Je kunt de servicenaam controleren in het * .services.yml-bestand. De servicenaam hoeft niet per se te beginnen met module_name.*, maar meestal is dat wel zo. Bijvoorbeeld:

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  return new static(
    $configuration,
    $plugin_id,
    $plugin_definition,
    $container->get('commerce_cart.cart_provider'),
    $container->get('entity_type.manager')
  );
}