Skip to content

Reihenfolge von Composer und Setup Events überarbeiten

Problembeschreibung

Installiert man mehrere voneinander abhängige Pakete, werden deren Install-Events und Setups in der "falschen" Reihenfolge ausgeführt.

Beispiel (Paketnamen müssen nicht der Wirklichkeit entsprechen):

  1. Ich installiere das Paket quiqqer/cashbook.
  2. Das Paket selbst wiederum requiret (unter anderem) quiqqer/currency.
  3. Das Setup vom Paket quiqqer/cashbook wird vor dem Setup von quiqqer/currency ausgeführt.
  4. In seinem Setup versucht quiqqer/cashbook über quiqqer/currency einen Wert aus der Datenbank abzurufen.
  5. Da das Setup von quiqqer/currency noch nicht ausgeführt wurde, existiert die notwendige Tabelle in der Datenbank noch nicht.
  6. Es gibt einen Fehler im Setup von quiqqer/cashbook

Im Beispiel betrifft das nur das Setup eines Pakets.
Das Problem tritt aber auch auf, wenn Pakete auf ihr onPackageInstall-Event gehen.
Composer installiert Pakete in willkürlicher (?) Reihenfolge und teilweise gleichzeitig.
So kann es passieren das ein Paket vor seinen Abhängigkeiten installiert wird.

Außerdem tritt das Problem auf, dass die Klassen eines neu installierten Pakets im onPackageInstall Event noch nicht im Autoloader bekannt sind.
Somit können in dem Event keine Klassen des Pakets aufgebaut werden.
Das führt bspw. zu Problemen, wenn das Paket selbst einen onPackageInstall Listener registriert (wie bspw. das quiqqer/watcher Paket).

Es besteht außerdem die Gefahr, dass während des Composer Prozesses Fehler auftreten.
Das könnte bspw. fehlender Arbeitsspeicher, eine volle Festplatte, fehlende Permissions, etc. sein.
Unter Umständen setzt Composer die Änderungen dann wieder zurück.
In so einem Fall könnte Pakete bereits gesetupt worden sein, obwohl ihre Klassen danach nicht mehr existieren.

Zusammengefasst liegen also folgende Probleme vor:

  1. Paket Setups werden in der falschen Reihenfolge ausgeführt
  2. Composer Events werden ohne Reihenfolge der Abhängigkeiten ausgeführt
  3. Neue Klassen fehlen im Autoloader bei Composer/Paket Events
  4. Pakete könnten gesetupt werden, obwohl die Installation später abbricht

Es handelt sich hierbei um akute Probleme, die alle auftreten, wenn man den ERP/ecoyn Stack in ein bestehendes QUIQQER System installieren möchte (bspw. für das ERP Testing Image).

Lösungsansatz

Gemeinsam mit @mor habe ich folgenden Lösungsansatz ausgearbeitet:

  • Die Events onPackageInstall, onPackageUpdate, etc. werden NICHT mehr sofort gefeuert, wenn Composer die Events auslöst
  • In den Composer Events wird gespeichert, dass dieses Event für ein Paket aufgetreten ist
  • Es wird gewartet bis Composer fertig und der Autoloader erstellt ist
  • Die gespeicherten Paket Events werden ausgelöst

Das löst die Probleme 3 (fehlender Autoloader) und 4 (Setup trotz abgebrochener Installation).

Um die anderen beiden Probleme zu lösen, müssen die Pakete in der Reihenfolge ihrer Abhängigkeiten gesetupt werden.
Das bedeutet, dass Pakete (Dependants) NACH ihren Abhängigkeiten (Dependencies) gesetupt werden müssen.

Die Abhängigkeiten könnten aus der composer.json gelesen werden.
Hierbei besteht aber das Problem, dass nicht unbedingt alle Abhängigkeiten für das Setup notwendig sind.
Außerdem kommt es schnell zu "Circular Dependencies", die nicht lösbar sind.
Beispiel: quiqqer/core required quiqqer/bricks und quiqqer/bricks required quiqqer/core.
Die Requirements aus der composer.json sind also nicht geeignet.

Ein Paket muss also separat festlegen können, wann sein Setup ausgeführt wird.
Das könnte beispielsweise "leicht" über einen Priority Wert geregelt werden.
Der Entwickler legt dann fest, dass sein Paket eine geringere Priorität als seine Abhängigkeiten hat.
Hier sehe ich allerdings das Problem bei Abhängigkeiten über mehrere Ebenen.
Beispiel:

  • quiqqer/core hat eine Priorität von 0
  • quiqqer/foo soll nach quiqqer/core und quiqqer/bar gesetupt werden. Die Priorität von quiqqer/bar ist aber noch nicht bekannt
  • quiqqer/bar soll nach quiqqer/core gesetupt werden Bei einer großen Anzahl von Abhängigkeiten muss der Entwickler also selbst großen Aufwand betreiben um herauszufinden, welchen Priorität sein Paket benötigt.

Das Problem der Reihenfolge ist ein bereits gelöstes Problem in der Graphentheorie.
Dazu wird ein "Dependency Graph" (ein gerichteter Graph) aufgebaut.
Die Knoten/Nodes des Graphen würden dabei durch die einzelnen Pakete repräsentiert werden.
Zwischen den Knoten besteht eine gerichtete Verbindung, wenn eine Abhängigkeit besteht.
Mit mathematischen Algorithmen kann man daraus dann eine bewiesen korrekte Reihenfolge der Abhängigkeiten bestimmen.

Für das Beispiel oben würde der Dependency Graph so aussehen:

flowchart TD
    quiqqer/foo --> quiqqer/core
    quiqqer/bar --> quiqqer/core
    quiqqer/foo --> quiqqer/bar

Daraus kann dann die Reihenfolge quiqqer/core -> quiqqer/bar -> quiqqer/foo berechnet werden.

Bei diesem Ansatz muss der Entwickler also nur noch festlegen, von welchen Paketen sein Paket abhängt.
QUIQQER liest diese Definitionen aus und baut daraus den Graphen.
Das Auslesen kann über Provider oder Einträge in der extras Sektion der composer.json erfolgen.
Mit einer fertigen Library (bspw. digilist/dependency-graph) kann der Graph erstellt und "gelöst" werden.
Die Setups und Composer Events der Pakete werden dann in der berechneten Reihenfolge ausgeführt.
Eine nicht lösbaren Abhängigkeitenreihenfolge kann von QUIQQER ohne Fehler behandelt und der Nutzer darüber informiert werden.
Beispielsweise kann solange kein Setup ausgeführt werden, bis die Konflikte beseitigt sind. Das aktuelle System funktioniert weiterhin.

Mit diesem Vorgehen lassen sich auch die Probleme 1 und 2 lösen.

Eine Proof of Concept habe ich hier bereits erstellt und erfolgreich getestet: https://dev.quiqqer.com/JanWennrich/quiqqer-package-setup-order
Sie basiert auf QUIQQER Providern und der oben genannten Library.
Die README in dem Repository enthält weitere Informationen.

TODO

Es muss diskutiert werden, ob das von mir vorgeschlagene Vorgehen funktionieren würde oder ob ich Probleme übersehen habe.

/cc @mor @henbug @peat

von Jan Wennrich bearbeitet