Container

Seit Shop 5.0 steht im JTL-Shop ein sogenannter „Dependency Injection Container“ zur Verfügung. In Zukunft wird ein Großteil aller JTL-Shop-Komponenten über diesen Container bereit gestellt. Zudem kann das Verhalten des Shops über die im Container registrierten Komponenten von Plugins modifiziert oder erweitert werden.

SOLID & Dependency Inversion

Der Container dient der Umsetzung des Dependency Inversion Principles. Zu diesem Themenkomplex gibt es viele Erklärungen im Internet. Wir empfehlen Entwicklern daher zunächst, sich mit SOLID und im Besonderen mit Dependeny Inversion vertraut zu machen.

Container / Komponente holen

<?php

use Services\JTL\PasswordServiceInterface;

$container       = Shop::Container();
$passwordService = $container->get(PasswordServiceInterface::class);
$randomPassword  = $passwordService->generate(12);

Wie hier zu sehen ist, können über den Container Dienste und andere Komponenten vom JTL-Shop bezogen werden. Der Container ist hierbei gemäß PSR-11 von der PHP-FIG entworfen. (https://www.php-fig.org/psr/psr-11/) Für den Fall, dass Sie eine IDE mit IntelliSense verwenden, haben wir zudem für alle vom JTL-Shop bereit gestellten Komponenten eine Methode zum Container hinzugefügt.

<?php

use Services\JTL\PasswordServiceInterface;

$container       = Shop::Container();
$passwordService = $container->getPasswordService();
$randomPassword  = $passwordService->generate(12);

Welche Komponenten vom JTL-Shop bereitgestellt werden, können Sie anhand der verfügbaren Methoden des Interfaces /includes/src/Services/DefaultServicesInterface.php einsehen.

Existenz prüfen

Sollten Sie prüfen wollen, ob eine Komponente bereit steht, können Sie dies wie folgt tun. (Hinweis: Alle in DefaultServicesInterface definierten Komponenten sind immer verfügbar).

<?php

use Services\JTL\PasswordServiceInterface;

$container = Shop::Container();
if ($container->has(PasswordServiceInterface::class)) {
    // die komponente existiert
}

Eigenen Komponente registrieren

Sie haben die Möglichkeit eigene Komponenten im Container zu registrieren. Hierzu benötigen Sie zunächst eine Klasse, die Sie bereitstellen wollen. Wir empfehlen, für jede Komponente ein Interface oder eine Abstrakte Klasse zu erstellen. Nur so kann das Decorator-Pattern eingesetzt werden (Siehe unten).

<?php

interface HelloWorldGeneratorInterface
{
    public function get();
}

class HelloWorldGenerator implements HelloWorldGeneratorInterface
{
    public function get()
    {
        return " Hello World ";
    }
}

Nun können Sie die entsprechende Komponente im Container registrieren:

<?php

$container = Shop::Container();
$container->setFactory(HelloWorldGeneratorInterface::class, function($container) {
    return new HelloWorldGenerator();
});

Nun steht ihre Komponente über den Container bereit und kann wie folgt abgerufen werden:

<?php

$container           = Shop::Container();
$HelloWorldGenerator = $container->get(HelloWorldInterface::class);
$HelloWorldGenerator->get(); // "Hello World" wird ausgegeben

Komponenten überschreiben

Sie können alle im Container registrierten Komponenten ersetzen. Voraussetzung hierfür ist, dass Sie das genutzte Interface implementieren oder, im Falle einer Abstrakten Klasse, von dieser erben. Hinweis: Wenn Sie Komponenten überschreiben, gilt dies für den gesamten Shop! Seien Sie also bitte vorsichtig und überschreiben Sie nur dann Komponenten, wenn Ihre Implementation zuverlässig funktioniert.

<?php

class TrimmedHelloWorldGenerator implements HelloWorldGeneratorInterface
{
    public function get()
    {
        return "Hello World";
    }
}

$container = Shop::Container();
$container->setFactory(HelloWorldGeneratorInterface:class, function($container) {
    return new TrimmedHelloWorldGenerator();
});

Komponenten erweitern (Decorator Pattern)

Sie können sämtliche über den Container bereitstehenden Komponenten (falls eine Abstrakte Klasse oder ein Interface bereit steht) mit hilfe des Decorator Patterns erweitern. Hierzu ein Beispiel, dass den HelloWorldContainer erweitert:

<?php

// Decorator Class
class TrimmingHelloWorldGeneratorDecorator implements HelloWorldGeneratorInterface
{
    protected $inner;

    public function __construct($inner)
    {
        $this->inner = $inner;
    }

    public function get()
    {
        return trim($this->inner->get());
    }
}

// Register Decorator

$container = Shop::Container();
$originalFactoryMethod = $container->getFactory(HelloWorldGeneratorInterface::class);
$container->setFactory(HelloWorldGeneratorInterface::class, function($container) use ($originalFactoryMethod) {
    $inner = $originalFactoryMethod($container);
    return new TrimmingHelloWorldGeneratorDecorator($inner);
});


// Use Component
$helloWorldGenerator = $container->get(HelloWorldGeneratorInterface::class);
echo $helloWordGenerator->get(); // return "Hello World" instead of " Hello World "

Factory oder Singleton

Wenn man eine Komponente im Container registriert, hat man die Möglichkeit zwischen einer Factory und einem Singleton zu wählen.

<?php
$container = Shop::Container();

$container->setSingleton(HelloWorldGeneratorInterface::class, function() { ... });
// oder
$container->setFactory(HelloWorldGeneratorInterface::class, function() { ... });

Nicht zu verwechseln ist dies mit der sogenannten „Factory Method“. Sowohl ein Singleton, als auch eine Factory benötigen eine Factory Method, welche die Erzeugung des jeweiligen Objektes übernimmt. Die Factory-Method kann sowohl für ein Singleton als auch für eine Factory auf die gleiche Weise geholt werden:

<?php
$container = Shop::Container();
$factoryMethod = $container->getFactoryMethod(HelloWorldGeneratorInterface::class);

Bei einem Singleton wird die Factory-Method nur ein einziges Mal abgerufen und nur ein einziges Objekt existiert Applikationsweit. Bei einer Factory, wird die Factory-Method bei jedem abruf erneut aufgerufen und ein neues Objekt erzeugt.

Hook zum Registrieren, Erweitern oder Überschreiben von Komponenten

Komponenten müssen möglichst früh registriert, erweitert oder überschrieben werden, da anstonsten Inkonstistenzen auftreten können. Daher sollte der Hook HOOK_GLOBALINCLUDE_INC (131) genutzt werden.

Hinweis: Manche Komponenten können nicht überschrieben werden, da diese schon im Voraus genutzt wurden. Daher sind folgende Komponenten nicht überschreibbar:

  • DbInterface