Container¶
Seit JTL-Shop 5.0.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 bereitgestellt. Zudem kann das Verhalten des Onlineshops über die im Container registrierten Komponenten von Plugins modifiziert oder erweitert werden.
SOLID & Dependency Inversion¶
Der Container dient der Umsetzung des "Dependency Inversion Principle".
Zu diesem Themenkomplex gibt es viele Erklärungen im Internet. Wir empfehlen Entwicklern daher zunächst, sich mit
SOLID und im Besonderen mit Dependency Inversion vertraut zu machen.
Container / Komponente holen¶
<?php
use JTL\Shop;
use JTL\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 von 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 von JTL-Shop bereitgestellten Komponenten eine Methode zum Container hinzugefügt.
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¶
Wenn Sie prüfen wollen, ob eine Komponente bereitsteht, können Sie dies wie folgt tun.
(Hinweis: Alle in DefaultServicesInterface
definierten Komponenten sind immer verfügbar.)
<?php
use JTL\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 = JTL\Shop::Container();
$container->setFactory(HelloWorldGeneratorInterface::class, function ($container) {
return new HelloWorldGenerator();
});
Nun steht die Komponente über den Container bereit und kann wie folgt abgerufen werden:
<?php
$container = JTL\Shop::Container();
$HelloWorldGenerator = $container->get(HelloWorldGeneratorInterface::class);
dump($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.
Attention
Wenn Sie Komponenten überschreiben, gilt dies für den gesamten Onlineshop! 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 bereitsteht) mit Hilfe des Decorator Pattern erweitern.
Hierzu ein Beispiel, das 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();
$original = $container->getFactoryMethod(HelloWorldGeneratorInterface::class);
$container->setFactory(HelloWorldGeneratorInterface::class, function ($container) use ($original) {
return new TrimmingHelloWorldGeneratorDecorator($original);
});
// Use Component
$helloWorldGenerator = $container->get(HelloWorldGeneratorInterface::class);
dump($helloWorldGenerator->get()); // return "Hello World" instead of " Hello World "
Factory oder Singleton¶
Wenn Sie eine Komponente im Container registrieren, haben Sie die Möglichkeit, zwischen einer Factory und einem Singleton zu wählen.
<?php
$container = JTL\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 ansonsten Inkonsistenzen
auftreten können. Daher sollte der Hook HOOK_GLOBALINCLUDE_INC
(131) genutzt werden.
Note
Manche Komponenten können nicht überschrieben werden, da diese schon im Voraus genutzt wurden.
Beispielsweise ist die Komponente "DbInterface" nicht überschreibbar.