Laravel-Abhängigkeiten und Composer sauber halten

Laravel Composer Abhängigkeiten wachsen mit den Jahren zu einem Dickicht, das Updates blockiert und die Sicherheit gefährdet. So halten Sie composer.json und den Dependency-Baum dauerhaft sauber.

Eine Laravel-Anwendung, die seit fünf oder acht Jahren im Produktivbetrieb läuft, hat ihren Wert längst bewiesen. Die Fachlogik funktioniert, die Kunden arbeiten damit. Was in solchen Projekten fast immer bröckelt, ist nicht der Kern, sondern der Code drumherum: allen voran die Abhängigkeiten. Die composer.json ist über die Jahre gewachsen, niemand weiß mehr genau, warum Paket X eigentlich drin ist, und ein composer update traut sich seit Monaten keiner mehr auszuführen. Das ist kein Zeichen für schlechten Code, sondern für fehlende Pflege an einer Stelle, die man leicht übersieht.

Dieser Artikel zeigt konkret, wie Sie Laravel Composer Abhängigkeiten wieder in einen Zustand bringen, in dem Updates berechenbar und Sicherheitslücken sichtbar werden.

Warum geraten Laravel Composer Abhängigkeiten überhaupt außer Kontrolle?

Abhängigkeiten geraten außer Kontrolle, weil jede Installation neuer Pakete transitive Unterabhängigkeiten mitzieht, die niemand bewusst ausgewählt hat. Ein einziges composer require kann zehn weitere Pakete in den Baum holen.

Über die Jahre entstehen so drei typische Probleme. Erstens sammeln sich Pakete an, die längst nicht mehr genutzt werden, weil ein Feature entfernt wurde, das require aber blieb. Zweitens hängen einzelne Pakete Versionen fest, sodass ein Update von Laravel selbst blockiert wird. Drittens verlieren Sie den Überblick, welche Pakete überhaupt noch gepflegt werden. Genau diese Punkte machen ein Framework-Upgrade später teuer, weil man erst das Dickicht durchdringen muss, bevor man überhaupt anfangen kann.

Wie verschaffe ich mir einen ehrlichen Überblick über den Dependency-Baum?

Den Überblick verschaffen Sie sich mit Composers eigenen Bordmitteln, bevor Sie irgendetwas ändern. Diese Befehle verändern nichts, sie zeigen nur den Ist-Zustand.

  • composer show --tree zeigt den kompletten Abhängigkeitsbaum und macht sichtbar, welches Ihrer direkten Pakete welche transitiven Pakete nachzieht.
  • composer why paketname beantwortet die entscheidende Frage, warum ein bestimmtes Paket überhaupt installiert ist und wer es verlangt.
  • composer why-not laravel/framework 11.0 erklärt konkret, welches Paket ein gewünschtes Upgrade blockiert.
  • composer outdated --direct listet nur Ihre direkten Abhängigkeiten auf, für die neuere Versionen existieren, ohne Sie mit hunderten transitiven Zeilen zu erschlagen.

Der Parameter --direct ist hier der wichtigste Hebel. Für den Alltag interessieren Sie fast nur die Pakete, die Sie selbst in die composer.json geschrieben haben. Um die transitiven Pakete kümmert sich Composer, solange die direkten sauber sind.

Wie unterscheide ich require von require-dev richtig?

In den Block require gehört ausschließlich, was die Anwendung im Produktivbetrieb zur Laufzeit braucht. Alles, was nur beim Entwickeln, Testen oder Debuggen nötig ist, gehört in require-dev.

In der Praxis ist diese Trennung oft verwaschen. Ich sehe regelmäßig, dass phpunit/phpunit, fakerphp/faker oder Debug-Tools wie barryvdh/laravel-debugbar im normalen require landen. Das hat zwei Folgen: Auf dem Produktivserver wird unnötiger Code installiert, und im schlimmsten Fall lässt sich eine Debug-Komponente aktivieren, die interne Informationen preisgibt. Verschieben Sie solche Pakete konsequent nach require-dev und deployen Sie mit composer install --no-dev --optimize-autoloader. So landet auf dem Server nur, was dort auch hingehört.

Wie finde ich Pakete, die gar nicht mehr gebraucht werden?

Ungenutzte Pakete finden Sie am zuverlässigsten mit einer statischen Analyse, die den Code gegen die deklarierten Abhängigkeiten prüft. Das manuelle Durchsuchen reicht bei gewachsenen Projekten nicht aus.

Ein bewährtes Werkzeug dafür ist composer-unused. Es meldet Pakete, die in der composer.json stehen, aber im Quellcode nirgends verwendet werden. Ergänzend zeigt composer-require-checker den umgekehrten Fall: Code, der Klassen oder Funktionen aus Paketen nutzt, ohne dass diese explizit als Abhängigkeit deklariert sind. Solche impliziten Abhängigkeiten sind gefährlich, weil sie unbemerkt kaputtgehen, sobald ein anderes Paket sie nicht mehr mitliefert. Prüfen Sie jeden Treffer einzeln, bevor Sie etwas entfernen, denn dynamisch geladene Pakete oder Service-Provider erkennt die statische Analyse nicht immer.

Wie mache ich Sicherheitslücken in Abhängigkeiten sichtbar?

Sicherheitslücken machen Sie mit composer audit sichtbar, das seit Composer 2.4 eingebaut ist. Der Befehl gleicht Ihre installierten Versionen gegen eine öffentliche Datenbank bekannter Schwachstellen ab und meldet betroffene Pakete.

Der entscheidende Schritt ist, diesen Befehl nicht einmalig, sondern automatisiert laufen zu lassen. Bauen Sie composer audit als festen Schritt in Ihre CI-Pipeline ein, sodass jeder Pull Request scheitert, wenn eine bekannte Lücke hinzukommt. Ergänzend hält ein Dienst wie Dependabot oder Renovate die composer.lock automatisch nach und öffnet Merge Requests für Sicherheitsupdates. Wichtig ist dabei die Erkenntnis: Eine gemeldete Lücke in einem transitiven Paket lässt sich oft nur beheben, indem Sie das direkte Paket aktualisieren, das es nachzieht. Genau hier hilft Ihnen das vorher gezeigte composer why.

Wie halte ich Versionsbeschränkungen berechenbar?

Berechenbar bleiben Versionen, wenn Sie in der composer.json mit dem Caret-Operator arbeiten und die composer.lock immer mit einchecken. Der Caret erlaubt kompatible Updates innerhalb einer Major-Version und blockiert Breaking Changes.

Ein Constraint wie "^11.0" erlaubt alle Versionen bis kurz vor 12.0 und folgt damit Semantic Versioning. Vermeiden Sie zwei Extreme. Feste Versionen wie "11.2.3" frieren alles ein und sammeln stillschweigend Sicherheitslücken an. Der Wildcard "*" dagegen lädt bei jedem Update unkontrolliert die neueste Version und ist eine Einladung für unerwartete Brüche. Die composer.lock gehört in jedem Fall ins Repository, denn nur sie garantiert, dass auf allen Umgebungen und beim Deployment exakt dieselben Versionen installiert werden. Ohne Lock-Datei ist jeder Server ein Zufallsprodukt.

Ein realistischer Ablauf für ein gewachsenes Projekt

Fangen Sie klein an, nicht mit dem großen Framework-Sprung. Ein sinnvoller erster Durchgang sieht so aus:

  1. Bestandsaufnahme mit composer outdated --direct und composer audit, ohne etwas zu ändern.
  2. require-dev aufräumen und Dev-Werkzeuge aus dem Produktiv-Block herausziehen.
  3. Mit composer-unused tote Pakete identifizieren und einzeln entfernen.
  4. Patch- und Minor-Updates in kleinen, testbaren Schritten einspielen, jeweils mit laufender Testsuite.
  5. Erst danach das eigentliche Major-Upgrade angehen, jetzt auf einem sauberen Fundament.

Ehrlicher Praxis-Tipp: Versuchen Sie nicht, alles an einem Nachmittag zu erledigen. Ein aufgeräumter Dependency-Baum entsteht durch viele kleine Commits, die jeweils grün durch die Tests laufen. Wenn ein Update etwas bricht, wollen Sie genau ein Paket im Diff sehen, nicht dreißig. Und wenn Ihnen bei genau diesem Aufräumen und dem anschließenden Framework-Upgrade eine erfahrene Hand fehlt, unterstützt LegacyWerk bei exakt solchen Modernisierungen bestehender Laravel-Anwendungen.

Vom Dickicht zum sauberen Baum Vorher Dev-Tools in require Tote Pakete unentdeckt Feste Versionen, Lücken Update blockiert Laravel Keiner traut sich updaten composer.lock fehlt Audit nie ausgefuehrt Nachher require-dev getrennt composer-unused geprueft Caret-Constraints ^11.0 why / why-not bekannt Kleine, gruene Updates Lock im Repository audit in der CI-Pipeline

Ist Ihre PHP-Anwendung noch zu retten?

Im kostenlosen Kurz-Check schaue ich mit Ihnen auf Ihr System und sage Ihnen ehrlich, was möglich ist – unverbindlich und ohne Verkaufsdruck.

Kurz-Check vereinbaren