Abschnitt 9 · Lektion 4
Eine zweite Abhängigkeit
In Lektion 9.3 hast du ein Paket installiert. In vendor/ lagen danach drei. Diese Lektion erklärt,
warum, und was daraus folgt.
Pakete benutzen selbst Pakete
{
"name": "hofladen/kasse",
"require": {
"php": "^8.2",
"ramsey/uuid": "^4"
}
} <?php
$wunsch = json_decode(file_get_contents("composer.json"), true);
$tatsache = json_decode(file_get_contents("composer.lock"), true);
$gewuenscht = [];
foreach ($wunsch["require"] as $name => $bereich) {
// php ist keine Bibliothek, sondern eine Bedingung an die
// Laufzeit. Es zaehlt hier deshalb nicht mit.
if ($name !== "php") {
$gewuenscht[$name] = $bereich;
}
}
echo "Gewuenscht: ", count($gewuenscht), "\n";
echo "Installiert: ", count($tatsache["packages"]), "\n\n";
foreach ($tatsache["packages"] as $paket) {
$woher = isset($gewuenscht[$paket["name"]]) ? "selbst gewaehlt" : "mitgebracht";
echo str_pad($paket["name"], 20), str_pad($paket["version"], 10), $woher, "\n";
} Die Autoren von ramsey/uuid haben sich beim Rechnen mit sehr großen Zahlen an dieselbe Regel
gehalten wie du: nicht selbst schreiben, was es schon gibt. Also steht in ihrer composer.json
ein require auf brick/math, und in brick/math steckt vielleicht noch etwas anderes.
Das nennt sich Abhängigkeitsbaum, und Composer läuft ihn vollständig ab, bevor es irgendetwas
lädt. Erst wenn eine Kombination gefunden ist, mit der alle Bedingungen gleichzeitig erfüllt sind,
wird heruntergeladen. Deshalb liegen in vendor/ mehr Ordner, als in deiner composer.json stehen.
ls vendor im Terminal zeigt brick, composer und ramsey: Der mittlere gehört Composer selbst,
dort liegt der Autolader, und unter ramsey liegen zwei Pakete, ls vendor/ramsey zeigt sie.
Das Beispiel liest beide Listen und stellt sie gegenüber: ein selbst gewähltes Paket, zwei
mitgebrachte. Ansehen kannst du dir denselben Baum auch mit einem Befehl: Tipp composer show --tree
ins Terminal oder nimm ihn aus der Befehlsleiste, während dieses Beispiel im Editor vorn liegt. Vier
Bedingungen stehen darin:
ramsey/uuidverlangtbrick/mathim Bereich>=0.8.16 <=0.18- und
ramsey/collectionals^1.2 || ^2.0, wobei der doppelte senkrechte Strich „oder” heißt - dazu selbst
php ^8.0 brick/mathwiederum verlangtphp ^8.2,ramsey/collectionverlangtphp ^8.1
Jede Zeile ist eine Bedingung, und alle müssen gleichzeitig stimmen. Die drei Angaben zu PHP sind
dabei die interessantesten: Auf einem Hosting mit PHP 8.1 wäre hier schon Schluss, weil brick/math
mehr verlangt. Composer sagt das vor dem Herunterladen und nicht danach. Andersherum fragt
composer why brick/math: Wer hat dieses Paket verlangt? Die Antwort ist eine Zeile, ramsey/uuid 4.9.3 requires brick/math (>=0.8.16 <=0.18). Die Versionen sind die aus der Maschine dieses
Kurses; auf deinem Rechner können sie neuer sein, die Form bleibt.
Nicht die neueste, sondern die neueste, die passt
Composer holt nicht stur die höchste Versionsnummer. Es sucht die höchste, mit der alle
Bedingungen zusammen aufgehen. Steht in deiner composer.json eine ältere Version von brick/math
fest, dann nimmt Composer eben eine etwas ältere Version von ramsey/uuid, nämlich die letzte, die
mit deiner Festlegung noch einverstanden ist.
Geht das nicht mehr auf, bricht die Installation ab. Composer nennt dann beide Seiten samt der
Bedingung, an der es scheitert, und schlägt vor, welche Angabe man lockern könnte. Das ist der
Moment, in dem eine feste Versionsnummer in der eigenen composer.json sich rächt: Sie hält nicht
nur dein Paket fest, sondern auch alles, was davon abhängt.
Und ein Paket, das nichts mitbringt
{
"name": "hofladen/kasse",
"require": {
"php": "^8.2",
"nikic/fast-route": "^1"
}
} <?php
// Dieselbe Auswertung, ein anderes Paket.
$wunsch = json_decode(file_get_contents("composer.json"), true);
$tatsache = json_decode(file_get_contents("composer.lock"), true);
$gewuenscht = [];
foreach ($wunsch["require"] as $name => $bereich) {
if ($name !== "php") {
$gewuenscht[$name] = $bereich;
}
}
echo "Gewuenscht: ", count($gewuenscht), "\n";
echo "Installiert: ", count($tatsache["packages"]), "\n\n";
foreach ($tatsache["packages"] as $paket) {
echo str_pad($paket["name"], 20), $paket["version"], "\n";
} nikic/fast-route ist das Gegenstück. Ein Wunsch, ein Paket, kein Baum. Die einzige Bedingung
darin ist eine PHP-Version, und die ist so alt, dass sie heute jeder erfüllt: composer show --tree
zeigt hier eine Zeile mit php >=5.4.0 darunter, sonst nichts.
Das ist eine Eigenschaft, auf die man beim Aussuchen achten kann. Ein Paket ohne Abhängigkeiten ist
leichter zu überblicken, aktualisiert sich seltener und kann dir keine Überraschung ins Projekt
holen, die du nie ausgesucht hast. Umgekehrt ist ein Baum kein Grund zur Panik: Die drei Pakete von
ramsey/uuid sind alle von derselben Sorte Leuten gepflegt.
Die Frage, die man vor jedem require stellen sollte, lautet deshalb nicht „hat es
Abhängigkeiten?”, sondern:
- Brauche ich das wirklich, oder sind es zehn Zeilen eigener Code?
- Wird das Paket gepflegt? Auf Packagist steht, wann die letzte Version erschien.
- Wie viel kommt mit? Und kenne ich, was mitkommt, wenigstens dem Namen nach?
Bei einer Kurzform für die Adresszeile aus Lektion 9.1 lautet die Antwort „Paket”. Bei einer Funktion, die zwei Zahlen addiert, lautet sie „selbst schreiben”, auch wenn es dafür ein Paket gibt.
Der Router, den du gleich brauchst
<?php
require __DIR__ . "/vendor/autoload.php";
$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute("GET", "/preise", "preisliste");
$r->addRoute("GET", "/bestellung/{nr:\d+}", "bestellseite");
});
foreach (["/preise", "/bestellung/77", "/bestellung/abc"] as $pfad) {
echo $pfad, ":\n";
var_dump($dispatcher->dispatch("GET", $pfad));
echo "\n";
} nikic/fast-route beantwortet eine einzige Frage: Zu welchem Stück Code gehört dieser Pfad? Genau
das braucht man ab Abschnitt 16, und dort baust du es selbst. Hier siehst du zuerst, wie es aussieht,
wenn es jemand fertig gebaut hat.
Zwei Dinge stehen im Beispiel:
simpleDispatcher()bekommt eine anonyme Funktion aus Lektion 6.6, und darin werden mitaddRoute()die Regeln angemeldet. Jede Regel besteht aus Methode, Pfadmuster und einem Wert, der für dich steht: hier eine Zeichenkette, später der Name einer Funktion.- In geschweiften Klammern steht ein Platzhalter.
{nr:\d+}heißt: Hier darf etwas stehen, es muss aus Ziffern bestehen, und es heißtnr. Ohne den Doppelpunkt und das Muster dahinter passt alles.
dispatch() gibt ein Array zurück, und das var_dump im Beispiel zeigt seinen Aufbau. An Stelle 0
steht die Auskunft: 1 heißt gefunden, 0 heißt nicht gefunden, 2 heißt „den Pfad gibt es, aber
nicht mit dieser Methode”. Wer die Zahlen nicht auswendig lernen will, vergleicht mit den Konstanten
FastRoute\Dispatcher::FOUND und FastRoute\Dispatcher::NOT_FOUND.
An Stelle 1 steht bei einem Treffer dein Wert, an Stelle 2 die Platzhalter als Zuordnung, also
["nr" => "77"]. Sieh dir dort den Typ an: Es ist eine Zeichenkette, keine Zahl, obwohl das
Muster nur Ziffern erlaubt hat. Alles, was aus einer Adresse kommt, ist Text, und das bleibt in
Abschnitt 10 und 11 so.
Beim dritten Pfad im Beispiel ist das Array dagegen nur einen Eintrag lang. Das ist kein
Sonderfall, den du abfangen musst, sondern die Folge daraus, dass es nichts weiter zu sagen gibt:
kein Treffer, kein Wert, keine Platzhalter. Wer nach dem Vergleich mit FOUND aussteigt, greift auf
die Stellen 1 und 2 ohnehin nicht mehr zu.
Zum Mitnehmen
Du installierst ein Paket und bekommst drei. Das ist kein Fehler und kein Trick, sondern die Rechnung, die man vor jedem require aufmachen sollte: Wie viel fremder Code kommt für diese eine Zeile ins Projekt?
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.