Rainer Feike IT Berater

Seit 15 Jahren Freelancer / Freiberufler in der EDV um München. Speziell C/C++, JAVA, PHP, Bank, Wertpapier. Projekt Profil.

Service Container, Dependency Injection in Drupal 8

Drupal Organisational Member Ich entwickle Drupal Projekte seit 2007 (ab Version 4.7) und biete kommerzielle Dienstleistungen im Enterprise Bereich an.

Mit Drupal 8 hat das Entwicklerteam großflächig auf Objektorientierung umgestellt. Dabei wurden viele alte Pattern der Drupal Modulentwicklung über Bord geworfen. Eines der populärsten dürfte dabei sicherlich das Hook-System sein.

Drupal 8 hat Abschied genommen vom Code-Discovery und setzt stattdessen nun auf lose gekoppelte Services die Interfaces implementieren (via Symfonie 2). Wer JAVA im Enterprise Umfeld entwickelt kennt das bereits. Die Patterns sind bekannt aus Spring und/oder CDI. Wer bisher bei der Drupal – Entwicklung ohne Objekt Orientierung auskam, ist spätestens mit Drupal 8 komplett verloren. Hier wird die OO at it’s best eingesetzt.

Ein wichtiges Pattern ist die Dependency Injection. Dabei instanziieren Objekte ihre Abhängigkeiten nicht selbst sondern überlassen das einem Container (Weld, Symfonie). Vielmehr werden die Abhängigkeiten konfiguriert bzw. deklariert.

Ein kurzes Beispiel:

Class Reifen implements RollableInterface {…};
Class Auto {
	RollableInterface meineReifen = new Reifen();
}

Das Auto braucht etwas rollbares und instanziiert sich deshalb Reifen im Constructor (quasi). Hier ist zwar später immerhin die Implementierung austauschbar (weil im Auto das RollableInterface genutzt wird), jedoch nicht ohne Eingriff in den Auto – Code (new Reifen() ist der Fehler). Bevor nun die Dependency Injection populär wurde, wurde dieser Sachverhalt durch das Factory – Pattern bereinigt. Die Factorys verlagern das Problem der konkreten Instanziierung aber nur um einige Ebenen und erzeugen dabei eine Menge Code. Darum wurden sie (sozusagen) zur Dependency Injection (DI) weiterentwickelt. Im Gegensatz zur Factory braucht die DI allerdings zur Laufzeit eine handelnde Komponente, nämlich den „Container“.

Das Pattern funktioniert nun so:
Deklaration: Service y existiert. Mein Service x hängt ab von y. Lieber Container, bitte gib mir eine Instanz von y in mein x Objekt. In JAVA (da ist das Prinzip einfacher verständlich):

Class Reifen implements RollableInterface {…};
Class Auto {
	@Inject RollableInterface meineReifen;
}

Der DI – Container (in JEE6 „CDI“ für Context Dependency Injection) erzeugt nun konfigurationsabhängig (im Falle von WELD bspw. über Position und Inhalt des beans.xml) ein passendes Objekt und übergibt das dem Auto. Die Konfigurationen der verschiedenen Systeme nutzen inzwischen vielfach Annotationen („@blabla“), früher waren XML Dateien üblich (Spring), Drupal 8 nutzt nun yml – Files (und Annotationen).

Will man nun die Implementierung des Rollable im Auto austauschen, ist kein Eingriff in die Autoklasse mehr nötig. Stattdessen wird der DI Container umkonfiguriert (Ein Schelm wer meint, da hätte sich die Komplexität nur verlagert – tatsächlich ergeben sich ein paar Vereinfachungen beim Build, Deployment und Staffing).

Jetzt mal konkret für Drupal 8.

Ein recht übersichtliches Beispiel lässt sich für die Brotkrumen (Breadcrumbs) bauen. Will man im eigenen Modul die Breadcrumbs anpassen, hatte Drupal 7 dafür den hook_menu_breadcrumb_alter vorgesehen. Man musste also eine Funktion meinModul_menu_breadcrumb_alter im Modulescope (einfach: in meinModul.module) implementieren. Drupal 7 hat dann bei Aktivierung des Moduls den Hook „discoverd“ und in die Callables (system table) eingetragen. Ab dann hat die Engine für jede Page einen Callback zu meinModul_menu_breadcrumb_alter abgesetzt. Das war recht pragmatisch gelöst, nach heutigem Stand der Softwareentwicklung aber ziemlich dirty.

Für Drupal 8 registrieren wir jetzt einen Service in meinmodul.services.yml:

services:
  meinmodul.breadcrumb:
    class: Drupal\meinmodul\MeinModulBreadcrumbBuilder
    arguments: ['@entity.manager', '@current_user']
    tags:
      - { name: breadcrumb_builder, priority: 1701 }

Damit haben wir dem Container Bescheid gegeben (das tag ist für die Registrierung zuständig, mehr über Services hier bei Drupal 8), nun müssen wir die Klasse noch erstellen (in /modules/meinmodul/src/MeinModulBreadcrumbBuilder.php – Achtung, das „src“ ist hier eine Symfonie 2 Feinheit) und darin das Interface BreadcrumbBuilderInterface implementieren:

<?php
/**
 * @file
 * Contains \Drupal\meinmodul\ProreosBreadcrumbBuilder.
 */
namespace Drupal\meinmodul;

use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;

/**
 * Provides a breadcrumb builder for meinmodul.
 */
class MeinModulBreadcrumbBuilder implements BreadcrumbBuilderInterface {

  /**
   * The node storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $nodeStorage;

  /**
   * The current user account.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $account;

  /**
   * Constructs the MeinModulBreadcrumbBuilder.
   *
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
   *   The entity manager service.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The current user account.
   */
  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $account) {
    $this->nodeStorage = $entity_manager->getStorage('node');
    $this->account = $account;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $route_match) {
  	return doSomeEntscheidungsLogic();
  }

  /**
   * {@inheritdoc}
   */
  public function build(RouteMatchInterface $route_match) {
    $book_nids = array();
    $links = array(Link::createFromRoute('HOME', ''));
    $links[] = Link::createFromRoute('meinModulSeite', 'anyAlias');
    return $links;
  }

}

So, fertig. Jetzt ruft Drupal 8 für jede Seite mein MeinModulBreadcrumbBuilder::applies() auf, und wenn der mit TRUE antwortet, dann werden die Links gemäß build() als Brotkrummen ausgegeben. Die Dependency Injection findet im Constructor __construct() statt. Die im services.yml deklarierten Abhängigkeiten (EntityManager und aktueller Benutzer) werden hier vom Container injected.

Ist das jetzt besser als der alte D7 – Hook?

  • Nein, weil es ist viel mehr Code.
  • Ja, weil der Code ist viel besser gekapselt (Pluspunkt Wartbarkeit).
  • Ja, weil die namespaces die Trennschärfe erhöhen.
  • Ja, weil die Implementierung entkoppelt ist.
  • Ja, weil mehr Typisierung im Spiel ist und daher der Code stabiler wird
  • Nein, weil sich der ganze Aufwand nur für langlebige Projekte lohnt bei denen über den Lebenszyklus häufiger Änderungsanforderungen („Change Requests“) auftreten.
  • Ja, weil die Objektorientierung das Prozedurale inzwischen so umfangreich abgelöst hat, dass man da eigentlich nicht mehr diskutieren muss.

Das war’s für heute, viel Spaß beim Rummachen.

Noch folgende Note von Gartner die ich vollstens unterstütze:
Web content management is no longer simply a tool for creating Web pages — it's now vital software for increasing the effectiveness of digital strategies. IT leaders responsible for WCM should consider the software's functions in the broader context in which they will deliver their full value.
Quelle: Magic Quadrant for Web Content Management.

Und Drupal ist in diesem Magic Quadrant prominent enthalten (via Acquia)!

Geschrieben am 07.11.2014, zuletzt bearbeitet am 07.11.2014, wie folgt kategorisiert:
Fachbereiche Technische Umgebung Stichworte
Rainer Feike: "Ich arbeite seit 1994 als Freelancer bzw. Freiberufler in der EDV Branche um München. Ich bin Softwareentwickler für Projekte in C/C++, JAVA und PHP. Ich bin Analyst und Berater im Fachbereich Bank und Börse. Ich konzipiere, entwickle und betreibe anspruchsvolle Web 2.0 Projekte. " Powered by Drupal

Skillset

C, C++, JAVA, PHP, Linux, Unix, SQL, mySQL, Oracle, JBF, Drupal, CSS, JavaScript, HTML, tomcat, XML, XSD - A highly efficient exceptional awesome software engineer :-)

Template design and technology by proxiss web20 technology