Zum Inhalt

Bootstrapping

Bootstrapping bezeichnet im Kontext von JTL-Shop die Initialisierung eines Plugins zur anschließenden Nutzung des EventDispatchers.

Struktur

Zentraler Einstiegspunkt des Bootstrappers ist die Datei Bootstrap.php im Hauptverzeichnis eines Plugins. In dieser Datei muss die Klasse Bootstrap im namespaces des Plugins angelegt werden und diese muss das Interface JTL\Plugin\BootstrapperInterface implementieren.

Das Interface sieht wie folgt aus:

/**
 * Interface BootstrapperInterface
 * @package JTL\Plugin
 */
interface BootstrapperInterface
{
    /**
     * @param Dispatcher $dispatcher
     */
    public function boot(Dispatcher $dispatcher);

    /**
     * @return mixed
     */
    public function installed();

    /**
     * @param bool $deleteData
     * @return mixed
     */
    public function uninstalled(bool $deleteData);

    /**
     * @return mixed
     */
    public function enabled();

    /**
     * @return mixed
     */
    public function disabled();

    /**
     * @param string $oldVersion
     * @param string $newVersion
     * @return mixed
     */
    public function updated($oldVersion, $newVersion);

    /**
     * @param int         $type
     * @param string      $title
     * @param null|string $description
     */
    public function addNotify($type, $title, $description = null);

    /**
     * @return PluginInterface
     */
    public function getPlugin(): PluginInterface;

    /**
     * @return DbInterface
     */
    public function getDB(): DbInterface;

    /**
     * @param DbInterface $db
     */
    public function setDB(DbInterface $db): void;

    /**
     * @return JTLCacheInterface
     */
    public function getCache(): JTLCacheInterface;

    /**
     * @param JTLCacheInterface $cache
     */
    public function setCache(JTLCacheInterface $cache): void;

    /**
     * @param string    $tabName
     * @param int       $menuID
     * @param JTLSmarty $smarty
     * @return string
     */
    public function renderAdminMenuTab(string $tabName, int $menuID, JTLSmarty $smarty): string;

    /**
     * @param LinkInterface $link
     * @param JTLSmarty     $smarty
     * @return bool
     */
    public function prepareFrontend(LinkInterface $link, JTLSmarty $smarty): bool;
}

Danger

Die Methode boot() der Klasse Bootstrap sollte ausschließlich dazu dienen, Hooks zu registrierten. Dieser Methode kommt eine zentrale Bedeutung zu: Sie wird bei jedem Frontend- UND Backend-Aufruf aufgerufen. Ein Fehler in boot() kann deshalb dazu führen, dass das komplette Backend (und somit auch die Möglichkeit, das fehlerhafte Plugin zu deinstallieren) versperrt ist.

Beispiele hierfür wären Programmierfehler wie Endlosschleifen, nicht antwortende Server von Drittanbietern und dergleichen. Ein "Stopp" der Applikation an dieser Stelle stoppt auch die Administrationsobfläche!

Implementierbare Methoden

Methode Hinweis zur Implementierung
installed() Wird unmittelbar nach der Installation eines Plugins aufgerufen. Bietet sich daher für Logik an, die einmalig ausgeführt werden muss, aber für Migrationen ungeeignet ist.
updated($oldVersion, $newVersion) Wird nach der Plugin-Aktualisierung über das Backend von JTL-Shop ausgeführt.
enabled() Wird ausgeführt, nachdem ein Plugin aktiviert wurde.
disabled() Wird ausgeführt, nachdem ein Plugin deaktiviert wurde.
boot(Dispatcher $dispatcher) Wird möglichst früh im Verlauf eines jeden Requests in JTL-Shop aufgerufen
(sowohl im Kontext des Front- und Backends als auch während eines
Abgleich mit JTL-Wawi).
uninstalled(bool $deleteData = true) Wird ausgeführt, nachdem ein Plugin im Backend komplett deinstalliert wurde.
Falls der Parameter TRUE ist, wünscht der Nutzer, dass Plugin-Daten
permanent gelöscht werden sollen (bspw. Datenbanktabellen).
renderAdminMenuTab(string $tabName, int $menuID, JTLSmarty $smarty) Kann genutzt werden, um HTML-Code für eigene Plugin-Tabs auszugeben,
beispielsweise via $smarty->fetch().
prepareFrontend(LinkInterface $link, JTLSmarty $smarty) Kann genutzt werden, um in Smarty eigene Variablen vor der Anzeige von
Fontend Links zu definieren.
Sie sollte in diesem Fall TRUE zurückgeben.

Der EventDispatcher

Innerhalb der boot()-Methode können EventListener registriert werden, die sich als flexiblere Alternative zu Hooks anbieten. Im Vergleich zu den via info.xml registrierten Hooks können EventListener dynamisch generiert werden.

Jeder Hook erzeugt automatisch ein Event mit dem Namen shop.hook.<HOOK-ID>. Um also beispielsweise den Hook HOOK_ARTIKEL_CLASS_FUELLEARTIKEL zu nutzen, lässt sich Folgendes innerhalb der boot()-Methode schreiben:

$dispatcher->listen('shop.hook.' . \HOOK_ARTIKEL_CLASS_FUELLEARTIKEL, function (array $args) {
    $args['oArtikel']->cName = 'Neuer Name';
});

Dies hat den Vorteil, dass der Listener in Abhängigkeit einer Plugin-Option registriert werden kann. Somit wird der Hook, anders als bei statischen Hooks, die in der info.xml registriert wurden, nicht immer ausgeführt. Auch muss so der objektorientierte Kontext des Bootstrappers nicht verlassen werden, während Hooks jeweils nur PHP-Dateien mit funktionalem Code aufrufen können.

Ab JTL-Shop 5.0.0 kann zudem auch die Priorität, ähnlich dem Hook-Knoten <priority> der info.xml, als dritter Parameter angegeben werden:

/**
 * @inheritdoc
 */
public function boot(Dispatcher $dispatcher)
{
    parent::boot($dispatcher);
    $dispatcher->listen(
        'shop.hook.' . \HOOK_ARTIKEL_CLASS_FUELLEARTIKEL,
        function () { /* do something */ },
        10
    );
}

Ab JTL-Shop 5.2.0 kann statt JTL\Events\Dispatcher::listen() auch die Methode JTL\Events\Dispatcher::hookInto(int $hookID, callable $listener, int $priority = 5) verwendet werden. Diese beiden Methoden sind identisch, bis auf die Tatsache, dass als erster Parameter direkt die HookID verwendet werden kann und keine Konkatenierung mit shop.hook. nötig ist.

/**
 * @inheritdoc
 */
public function boot(Dispatcher $dispatcher)
{
    parent::boot($dispatcher);
    $dispatcher->hookInto(
        \HOOK_ARTIKEL_CLASS_FUELLEARTIKEL,
        function () { /* do something */ }
    );
}

Siehe auch Abschnitt "Die info.xml", Plugin-Hooks.

Innerhalb des Bootstrappers besteht via $this->getPlugin() immer Zugriff auf die Instanz des Plugins, sodass die Nutzung des PluginHelpers vermieden werden kann. Auch besteht via $this->getDB() Zugriff auf die Datenbank sowie via $this->getCache() auf den Objektcache. Es ist daher nicht nötig, diese Instanzen über den DI-Container Shop::Container()->getDB() oder Shop::Container()->getCache() zu holen.