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):
- Ich installiere das Paket
quiqqer/cashbook
. - Das Paket selbst wiederum requiret (unter anderem)
quiqqer/currency
. - Das Setup vom Paket
quiqqer/cashbook
wird vor dem Setup vonquiqqer/currency
ausgeführt. - In seinem Setup versucht
quiqqer/cashbook
überquiqqer/currency
einen Wert aus der Datenbank abzurufen. - Da das Setup von
quiqqer/currency
noch nicht ausgeführt wurde, existiert die notwendige Tabelle in der Datenbank noch nicht. - 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:
- Paket Setups werden in der falschen Reihenfolge ausgeführt
- Composer Events werden ohne Reihenfolge der Abhängigkeiten ausgeführt
- Neue Klassen fehlen im Autoloader bei Composer/Paket Events
- 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 von0
-
quiqqer/foo
soll nachquiqqer/core
undquiqqer/bar
gesetupt werden. Die Priorität vonquiqqer/bar
ist aber noch nicht bekannt -
quiqqer/bar
soll nachquiqqer/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.