Webentwicklung

Composer-Lock-Dateien für kleine PHP-Anwendungen - Geprüfte Abhängigkeiten bereitstellen

Composer-Lock-Dateien für kleine PHP-Anwendungen - Geprüfte Abhängigkeiten bereitstellen

Eine PHP-Anwendung kann am Montag erfolgreich bereitgestellt werden und am Freitag scheitern, obwohl sich ihr eigener Quellcode nicht geändert hat. Eine mögliche Ursache wird leicht übersehen: Beide Deployments haben nicht dieselben Abhängigkeitsversionen erhalten. Eine Versionsbedingung wie ^3.0 beschreibt einen Bereich, nicht eine einzige unveränderliche Paketversion.

Composer begegnet diesem Problem mit composer.lock. Die Lock-Datei ist einfach genug, um sie zu committen und anschließend zu vergessen. Gerade diese Einfachheit kann jedoch zwei gegensätzliche Fehler verdecken: sie vollständig zu ignorieren oder sie als Beweis dafür zu behandeln, dass die gesamte Produktionsumgebung reproduzierbar ist. Beides trifft nicht ganz zu.

Dieser Artikel stellt deshalb eine engere Frage: Wie kann eine kleine PHP-Anwendung genau den Abhängigkeitssatz bereitstellen, der tatsächlich geprüft wurde, ohne zu verschweigen, was die Lock-Datei nicht kontrolliert?

Das Manifest als Richtlinie, die Lock-Datei als Momentaufnahme

composer.json legt fest, was das Projekt verwenden darf. Die Datei kann PHP, Extensions, Anwendungspakete, Entwicklungswerkzeuge und Versionsbereiche verlangen. Composer löst diese Bedingungen anschließend gemeinsam mit allen transitiven Abhängigkeiten auf.

composer.lock zeichnet das Ergebnis dieser Auflösung auf. Für eine Anwendung ist folgendes Denkmodell hilfreich:

  • composer.json ist die Abhängigkeitsrichtlinie.
  • composer.lock ist die geprüfte Momentaufnahme, die unter dieser Richtlinie ausgewählt wurde.
  • vendor/ ist eine Installation dieser Momentaufnahme für ein bestimmtes Release.

Das ist ein Erklärungsmodell und keine offizielle Composer-Terminologie, erfasst aber einen wichtigen Unterschied. Laut der Composer-Dokumentation zur grundlegenden Verwendung löst update Abhängigkeiten auf und schreibt ihre exakten Versionen in die Lock-Datei. Ist bereits eine Lock-Datei vorhanden, verwendet install die darin verzeichneten Versionen.

Bei einer Anwendung sollte die Lock-Datei normalerweise zusammen mit dem Quellcode committet werden. Dadurch können Entwicklungsrechner, CI-Jobs und Deployment-Prozesse dieselben Paketversionen anfordern. Für wiederverwendbare Bibliotheken gelten andere Überlegungen, weil deren Nutzer die Abhängigkeitsauflösung durchführen. Das liegt jedoch außerhalb des Umfangs dieses Artikels.

update trifft eine Entscheidung, install wendet sie an

Die Namen der Befehle klingen ähnlich, doch sie gehören in unterschiedliche Phasen.

composer update fordert Composer auf, den Abhängigkeitsgraphen erneut aufzulösen. Eine Bedingung kann nun ein neueres Paket auswählen, und auch transitive Pakete können sich ändern. Der entstehende Diff der Lock-Datei gehört deshalb zu der vorgeschlagenen Änderung. Er verdient dieselbe Prüfung und dieselben Tests wie ein Diff im Anwendungscode.

composer install wendet bei vorhandener Lock-Datei die aufgezeichnete Auswahl an. Ein direktes update während des Deployments verbindet zwei Vorgänge, die sicherer getrennt bleiben: die Auswahl neuen Drittanbieter-Codes und die Veröffentlichung der Anwendung. Ein fehlgeschlagenes Deployment lässt sich dann schwerer erklären, weil der Server eine Abhängigkeitsentscheidung getroffen hat, die im geprüften Commit nicht enthalten war.

Ein überschaubarer Ablauf sieht so aus:

  1. Ein geplantes Abhängigkeitsupdate in einem Entwicklungsbranch oder einer Wartungsänderung ausführen.
  2. composer.json und composer.lock prüfen.
  3. Die Anwendungstests und relevante Smoke-Tests ausführen.
  4. Die Lock-Datei zusammen mit der Änderung committen.
  5. Diesen Commit mit composer install bereitstellen, nicht mit einem ungeprüften composer update.

Damit werden Updates nicht risikofrei. Die Entscheidung wird jedoch sichtbar, testbar und leichter umkehrbar.

Eine kleine Deployment-Sequenz

Der genaue Release-Prozess hängt von der Anwendung ab. Die folgenden Befehle zeigen jedoch Verantwortlichkeiten, die irgendwo in der Pipeline abgebildet sein sollten. Sie stehen bewusst einzeln; ein Deployment-System sollte anhalten, sobald ein erforderliches Gate fehlschlägt.

1. Manifest und Lock-Datei validieren

composer validate --strict

Die Composer-CLI-Referenz erklärt, dass validate die composer.json und bei vorhandener Lock-Datei auch deren Aktualität gegenüber dem Manifest prüft. Mit --strict führen auch Warnungen zu einem Exit-Status ungleich null. Das ist ein nützliches Konsistenz-Gate, aber kein Ersatz für Anwendungstests.

2. Den geprüften Produktionssatz installieren

composer install --no-dev --optimize-autoloader --no-interaction

--no-dev überspringt Pakete aus require-dev und Entwicklungsregeln für das Autoloading. Das verringert reine Entwicklungsabhängigkeiten in der Produktion, deckt aber auch einen Konfigurationsfehler auf, wenn Laufzeitcode insgeheim von einem Entwicklungspaket abhängt. Ein solcher Fehler sollte in den Abhängigkeitsdeklarationen behoben und nicht dadurch verdeckt werden, dass alle Entwicklungswerkzeuge in der Produktion installiert werden.

--optimize-autoloader wandelt PSR-0- und PSR-4-Regeln in eine Class Map um. Composers Leitfaden zur Autoloader-Optimierung empfiehlt diese erste Optimierungsstufe für die Produktion. Die aggressivere Option --classmap-authoritative ist jedoch kein universelles Upgrade: Derselbe Leitfaden warnt, dass sie Software beeinträchtigen kann, die Klassen zur Laufzeit erzeugt.

--no-interaction verhindert, dass ein Deployment unbegrenzt auf eine Eingabe im Terminal wartet. Die Option beantwortet nicht automatisch jede betriebliche Frage; Plugin-Berechtigungen und Authentifizierung brauchen weiterhin explizite, vorab konfigurierte Entscheidungen.

3. Die tatsächliche Laufzeitplattform prüfen

composer check-platform-reqs --no-dev

Composer stellt PHP, Extensions und einige Systembibliotheken als Plattformpakete dar. Eine Lock-Datei kann Paketversionen bewahren, während dem Zielserver weiterhin ext-intl fehlt, dort eine inkompatible PHP-Version läuft oder eine andere erforderliche Plattformfähigkeit abweicht.

Die Dokumentation zu check-platform-reqs erklärt, dass dieser Befehl die reale PHP- und Extension-Umgebung prüft und simulierte Werte aus config.platform ignoriert. Er sollte auf dem Deployment-Ziel oder im finalen Laufzeit-Image laufen, nicht nur auf einem Entwicklungsrechner.

4. Anwendungsspezifische Prüfungen vor der Verkehrsumstellung

Kein allgemeiner Composer-Befehl kann beweisen, dass Routen gerendert werden, der Datenbankzugriff funktioniert, die Konfiguration vorhanden ist oder ein Queue-Consumer starten kann. Ein Release braucht weiterhin Prüfungen, die zur Anwendung passen. Bei einer kleinen Website können das ein Health-Endpunkt, eine schreibgeschützte Datenbankoperation und Anfragen an einige zentrale Seiten sein.

Wenn möglich, sollte das neue Release in einem separaten Verzeichnis erstellt und erst nach erfolgreichen Prüfungen aktiviert werden. Wird das vorherige Release aufbewahrt, besteht ein Rollback aus einem Verzeichnis- oder Symlink-Wechsel, statt während eines Vorfalls den Abhängigkeitsgraphen von gestern rekonstruieren zu müssen.

Die Plattformkonfiguration ist ein Ziel, kein Beweis

Composers Option config.platform kann während der Abhängigkeitsauflösung eine Zielversion von PHP oder einer Extension simulieren. Das ist nützlich, wenn die Entwicklungsumgebung eine neuere PHP-Version als die Produktion verwendet: Der Resolver kann so daran gehindert werden, Pakete auszuwählen, die mehr verlangen, als die Produktion bereitstellt.

Eine simulierte Plattform kann die Wirklichkeit jedoch auch verbergen. Behauptet die Konfiguration PHP 8.x, während ein Befehl tatsächlich mit einem älteren Interpreter läuft, kann die Auflösung gelingen und die Anwendung zur Laufzeit scheitern. Composer empfiehlt deshalb, diese Einstellung mit check-platform-reqs zu kombinieren.

Es gibt zwei getrennte Fragen:

  • Wurde der Abhängigkeitssatz für das beabsichtigte Ziel aufgelöst?
  • Erfüllt die Maschine, die ihn ausführen soll, dieses Ziel tatsächlich?

config.platform hilft bei der ersten Frage. Eine Prüfung gegen die reale Laufzeit hilft bei der zweiten.

Skripte und Plugins machen die Installation zu mehr als Dateikopien

Es liegt nahe, composer install nur als Herunterladen von Archiven zu beschreiben. Das ist unvollständig. Composer unterstützt Skripte und Plugins. Die Sicherheits-FAQ weist darauf hin, dass sie Drittanbieter-Code mit den Rechten des Kontos ausführen können, unter dem Composer läuft.

Daraus folgen zwei praktische Vorsichtsmaßnahmen. Erstens sollte Composer nicht allein deshalb als root ausgeführt werden, weil das Deployment Dateien schreiben muss; ein eigenes Deployment-Konto sollte nur die benötigten Rechte erhalten. Zweitens muss bekannt sein, welche Skripte und Plugins die Anwendung erwartet. Die Optionen --no-scripts und --no-plugins verringern die Codeausführung, können bei blindem Einsatz aber notwendige Anwendungsschritte überspringen. Sie sind Richtlinienentscheidungen und keine dekorativen Hardening-Schalter.

Die Lock-Datei macht den ausgewählten Paketsatz prüfbar. Sie macht nicht jedes ausgewählte Paket vertrauenswürdig und neutralisiert keinen Code, der während der Installation legitim ausgeführt wird.

Was die Lock-Datei nicht sperrt

Composer dokumentiert, dass wiederholte Installationen derselben Lock-Datei bis auf Zeitstempel identische Inhalte in vendor/ erzeugen können. Das ist eine wertvolle und recht spezifische Zusage. Sie sollte nicht zu der Aussage erweitert werden, alle Server seien identisch.

Die Lock-Datei allein fixiert nicht:

  • den PHP-Interpreter und seine Konfiguration;
  • installierte PHP-Extensions und verknüpfte Systembibliotheken;
  • die in der Pipeline verwendete Composer-Executable;
  • Betriebssystempakete, Umgebungsvariablen, Secrets und Service-Konfiguration;
  • Datenbankschema und persistente Daten;
  • externe Dienste, die von der Anwendung aufgerufen werden;
  • die Auswirkungen von Composer-Skripten, Plugins oder Build-Schritten der Anwendung.

Ein robusteres Deployment zeichnet diese Ebenen getrennt auf oder kontrolliert sie: beispielsweise durch ein fixiertes Container-Basisimage oder einen dokumentierten Server-Paketsatz, eine bekannte Composer-Version, Schema-Migrationen, Konfigurationsvalidierung und ein aufbewahrtes Release-Artefakt. Nicht jede persönliche Website benötigt eine aufwendige Artefaktplattform. Jede Website profitiert aber davon, zu wissen, welche Annahmen außerhalb von Composer verbleiben.

Gesperrt bedeutet nicht gepflegt

Eine Lock-Datei verhindert bewusst überraschende Upgrades. Dieselbe Stabilität kann eine alte verwundbare oder aufgegebene Abhängigkeit erhalten, wenn niemand Updates prüft. Wiederholbarkeit und Pflege sind unterschiedliche Ziele.

Abhängigkeitsupdates sollten bewusst erfolgen und nicht dauerhaft vermieden werden. Composer bietet die Befehle outdated und audit zur Unterstützung der Prüfung an, doch ihre Ausgabe braucht weiterhin Kontext. Ein Sicherheitshinweis kann die konkrete Verwendung eines Pakets betreffen oder auch nicht, während selbst ein scheinbar kleines Update Tests verdienen kann. Hilfreich ist ein regelmäßiges Wartungsfenster: verfügbare Änderungen prüfen, einen begrenzten Satz aktualisieren, testen, den Lock-Diff kontrollieren und anschließend veröffentlichen.

Auch ein Rollback braucht klare Grenzen. Während eines Ausfalls eine ältere Lock-Datei wiederherzustellen und neu zu installieren, hängt weiterhin von erreichbaren Repositories und der Umgebung ab. Das zuletzt funktionierende Release-Artefakt aufzubewahren ist meist die ruhigere Lösung. Enthält ein Release eine nicht umkehrbare Datenbankmigration, kann ein Rollback nur der PHP-Dateien unsicher sein. Das Zurücksetzen von Abhängigkeiten muss deshalb zum umfassenderen Migrationsplan der Anwendung passen.

Ein enges Versprechen ist ein nützliches Versprechen

composer.lock kann ein vollständiges PHP-Deployment nicht allein reproduzierbar machen. Die Datei kann etwas Kleineres und Wesentliches leisten: die geprüfte und getestete Abhängigkeitsentscheidung bewahren.

Das betriebliche Muster ist überschaubar. Updates außerhalb der Produktion auflösen, die Lock-Datei committen und prüfen, sie validieren, beim Deployment installieren statt aktualisieren, die reale Laufzeitplattform kontrollieren, anwendungsspezifische Smoke-Tests ausführen und einen Rollback-Pfad erhalten. Die verbleibenden Variablen sollten anschließend dokumentiert werden, statt so zu tun, als seien sie verschwunden.

Dieses engere Versprechen ist keine Schwäche. Es erspart einem Deployment eine Frage zum denkbar schlechtesten Zeitpunkt: „Welche Abhängigkeitsversionen hat der Server heute ausgewählt?“

References