Drupal 8 Dependency Injection im Controller

Geschrieben am 10.11.2014
Drupal Org

Ich entwickle Drupal Projekte seit 2007 und biete kommerzielle Dienstleistungen im Enterprise Bereich an.

Zuletzt hatte ich beschrieben, wie Services und Dependency Injection in Drupal 8 funktionieren. Heute zeige ich, wie man eigene Services in Controllern verwendet.

Motivation: Wenn wir uns schon zugunsten Wartbarkeit und Testbarkeit den ganzen Heckmeck mit DI aufhalsen, dann sollen auch Controller davon profitieren. Und bei Symfonie 2 sind Controller keine Services und können daher nicht mit Constructor-Injection versorgt werden.

Controller verwenden wir in Drupal 8 in erster Linie, um unsere Routings darzustellen (früher hook_menu).

Ein Beispiel aus proreos.routing.yml:

proreos.verzeichnis.kanzlei:
  path: '/verzeichnis/{kanzleinode}'
  defaults:
    _content: '\Drupal\proreos\Controller\VerzeichnisController::kanzleiView'
    _title_callback: '\Drupal\proreos\Controller\VerzeichnisController::kanzleiViewTitle'
  requirements:
    _permission: 'access content'

Diese Route - Deklaration sagt Drupal, das der Output für http:://bla/verzeichnis/xx durch den Aufruf der Methode kanzleiView() in der Klasse proreos\Controller\VerzeichnisController gerendert werden soll. Wie kann ich aber nun in VerzeichnisController Services verwenden?

Dazu muss man wissen, dass Drupal 8 den Controller (Request scoped) erzeugt indem das Framework create(ContainerInterface $container) aufruft. Wir müssen also create() in unserem Controller überschreiben (geerbet von Drupal\Core\Controller\ControllerBase was auch eine Basisklasse unseres Controllers sei). Und damit sich ein einheitliches Bild zur Constructor-Injection der Services ergibt hat sich folgende Notation eingebürgert:

/**
   * Hold the proreos super manager.
   *
   * @var \Drupal\proreos\ProreosSuperManager
   */
  protected $proreosSuperManager;
	
  /**
   * Constructs the VerzeichnisController.
   *
   * @param \Drupal\proreos\ProreosSuperManager $super_manager
   *   The proreos super manager.
   */
  public function __construct(ProreosSuperManager $super_manager) {
    $this->proreosSuperManager= $super_manager;
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
	  $container->get('proreos.supermanager')
    );
  }

Die Methode create() ruft also unseren Constructor mit dem gewünschten Argument auf und holt vorher den Service beim Container ab.

Das sollte man sich übrigens nicht einfacher machen, indem man die Methode container() aus dem BaseController nimmt. Die verwendet nämlich deprecated den statischen Context (Drupal::getContainer()).
Aber jetzt sieht alles wieder aus wie Constructor-Injection bei Symfonie 2. Wegen der ganzen Type Hints und Annotationen ist es auch in Eclipse PDT recht komfortabel zu benutzen.

IT-Skills
Fachbereich
Stichworte