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

Gebruik van PHP Constructor Property Promotion in aangepaste Drupal-modules

02/09/2025, by Ivan

Menu

PHP 8 introduceerde constructor property promotion, een functie die het definiëren en toewijzen van klasse-eigenschappen vereenvoudigt door je toe te staan deze direct in de constructorhandtekening te declareren en initialiseren. Deze tutorial toont hoe je constructor property promotion gebruikt in aangepaste Drupal-modules (die PHP 8.0+ vereisen), specifiek om dependency injection in je services en controllers te vereenvoudigen. We vergelijken het traditionele Drupal-patroon (gebruikt in PHP 7 en vroege Drupal 9) met de moderne PHP 8+-benadering, met volledige codevoorbeelden voor beide. Aan het einde zul je zien hoe deze moderne syntaxis boilerplate vermindert, de code duidelijker maakt en aansluit bij huidige best practices.

Drupal 10 (dat PHP 8.1+ vereist) is begonnen met het adopteren van deze moderne PHP-functies in de core, dus ontwikkelaars van aangepaste modules worden aangemoedigd hetzelfde te doen. Laten we beginnen met het bekijken van het traditionele patroon voor dependency injection in Drupal, en het vervolgens herschrijven met behulp van constructor property promotion.

Traditionele Dependency Injection in Drupal (Pre-PHP 8)

In Drupal-services en -controllers omvat het traditionele patroon voor dependency injection drie stappen:

  1. Declareer elke dependency als een klasse-eigenschap (meestal protected) met een passende docblock.

  2. Gebruik type hints voor elke dependency in de constructorparameters en wijs ze toe aan de klasse-eigenschappen in de constructor.

  3. Voor controllers (en sommige plugin-klassen), implementeer een statische create(ContainerInterface $container)-methode om services uit Drupal’s servicecontainer op te halen en de klasse te instantieren.

Dit resulteert in behoorlijk wat boilerplate-code. Bijvoorbeeld, een eenvoudige serviceklasse die de configuration factory en een logger factory nodig heeft, zou traditioneel als volgt worden geschreven:

Voorbeeld van een traditionele serviceklasse

<?php

namespace Drupal\example;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

/**
 * Voorbeeldservice die de sitenaam logt.
 */
class ExampleService {
  /**
   * De configuration factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * De logger channel factory service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * Constructor voor ExampleService.
   */
  public function __construct(ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory) {
    $this->configFactory = $config_factory;
    $this->loggerFactory = $logger_factory;
  }

  /**
   * Logt de sitenaam.
   */
  public function logSiteName(): void {
    $site_name = $this->configFactory->get('system.site')->get('name');
    $this->loggerFactory->get('example')->info('Site name: ' . $site_name);
  }
}

De bijbehorende service moet ook worden gedefinieerd in het services YAML-bestand van de module, zoals:

# example.services.yml
services:
  example.example_service:
    class: Drupal\example\ExampleService
    arguments:
      - '@config.factory'
      - '@logger.factory'

Wanneer Drupal deze service instantieert, worden de opgegeven argumenten in volgorde doorgegeven aan de constructor.

Voorbeeld van een traditionele controllerklasse

Drupal-controllers kunnen ook dependency injection gebruiken. Typisch erft een controllerklasse van ControllerBase en implementeert Drupal’s containerinjectie door een create()-methode te definiëren. Deze methode haalt services uit de container en geeft ze door aan de constructor:

<?php

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Controller voor Example-routes.
 */
class ExampleController extends ControllerBase {
  /**
   * Entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * String translation service.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $stringTranslation;

  /**
   * Constructor.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
    $this->entityTypeManager = $entity_type_manager;
    $this->stringTranslation = $string_translation;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('entity_type.manager'),
      $container->get('string_translation')
    );
  }

  /**
   * Bouwt een eenvoudige pagina.
   */
  public function build(): array {
    $node_count = $this->entityTypeManager->getStorage('node')->getQuery()->count()->execute();
    return [
      '#markup' => $this->t('Er zijn @count nodes op de site.', ['@count' => $node_count]),
    ];
  }
}

Gebruik van Constructor Property Promotion (PHP 8+) in Drupal

Constructor property promotion vereenvoudigt dit patroon door je toe te staan eigenschappen rechtstreeks te declareren en toe te wijzen in de constructorhandtekening. Door een zichtbaarheidsspecificatie (zoals protected) toe te voegen aan de constructorparameter, genereert PHP automatisch de eigenschap en kent deze toe – je hoeft dit niet handmatig te doen in de body van de constructor.

Dit is puur syntactische suiker. Het verandert niets aan hoe dependency injection in Drupal werkt. Services worden nog steeds geregistreerd in YAML (of automatisch gekoppeld), en controllers gebruiken nog steeds create() tenzij ze als service geregistreerd zijn. Het verschil zit hem enkel in de manier waarop je de klasse schrijft.

Laten we onze voorbeelden herschrijven met property promotion.

Serviceklasse met property promotion

<?php

namespace Drupal\example;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;

/**
 * Voorbeeldservice (met property promotion).
 */
class ExampleService {
  public function __construct(
    protected ConfigFactoryInterface $configFactory,
    protected LoggerChannelFactoryInterface $loggerFactory
  ) {}

  public function logSiteName(): void {
    $site_name = $this->configFactory->get('system.site')->get('name');
    $this->loggerFactory->get('example')->info('Site name: ' . $site_name);
  }
}

Controllerklasse met property promotion

<?php

namespace Drupal\example\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Controller (met property promotion).
 */
final class ExampleController extends ControllerBase {
  public function __construct(
    private EntityTypeManagerInterface $entityTypeManager,
    private TranslationInterface $stringTranslation
  ) {}

  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('entity_type.manager'),
      $container->get('string_translation')
    );
  }

  public function build(): array {
    $node_count = $this->entityTypeManager->getStorage('node')->getQuery()->count()->execute();
    return [
      '#markup' => $this->t('Er zijn @count nodes op de site.', ['@count' => $node_count]),
    ];
  }
}

Voordelen van Constructor Property Promotion

  • Minder Boilerplate: Je schrijft aanzienlijk minder code. Geen aparte propertydeclaraties en constructor-assignments meer.
  • Duidelijker en compacter: Alle dependencies staan duidelijk in de constructor, wat de leesbaarheid vergroot.
  • Minder documentatie nodig: Omdat de types in de constructor staan, zijn @var en @param docblocks vaak overbodig.
  • Moderne PHP-syntaxis: Je code sluit aan bij de moderne Drupal- en PHP-standaarden en is klaar voor toekomstige verbeteringen zoals readonly.

Performance en functionaliteit blijven gelijk – property promotion is een syntactisch hulpmiddel dat de logica niet beïnvloedt. Je blijft volledig type-gespecificeerde properties gebruiken binnen je klasse.

Conclusie

Constructor property promotion is een eenvoudige maar krachtige functie in PHP 8 waarmee Drupal-ontwikkelaars hun aangepaste modules overzichtelijker en moderner kunnen maken. Door de boilerplate te verwijderen, kun je je focussen op de eigenlijke functionaliteit van je klassen. We hebben getoond hoe je een typische Drupal-service en controller herschrijft met promoted properties, en de voordelen besproken. De code wordt korter, duidelijker en consistenter met moderne standaarden – een aanrader voor elke ontwikkelaar.