Composer Patches in Laravel: Vendor-Code fixen ohne Fork

7 Min. Lesezeit Read in English

Ein Fremdpaket hat einen Bug. Der Fix ist eine einzige Zeile in vendor/. Du änderst sie, alles läuft, du bist zufrieden. Beim nächsten composer update ist die Zeile wieder weg.

Genau hier stehen die meisten Laravel-Teams irgendwann. Zwischen “schnell in vendor/ reinhacken” und “das ganze Paket forken” liegt ein sauberer Mittelweg: ein Patch, der bei jedem Composer-Lauf automatisch wieder angewendet wird.

Warum vendor/ kein Ort für Fixes ist

vendor/ gehört Composer, nicht dir. Der Ordner steht in .gitignore, wird nicht committet und bei jedem composer install neu aus den Paketquellen aufgebaut. Eine Änderung dort lebt nur auf deinem Rechner.

Das fällt spätestens im Team auf. Deine Kollegin zieht den Branch, ihr vendor/ kennt deinen Fix nicht. Die CI baut frisch, der Fix fehlt, der Build ist grün, das Feature trotzdem kaputt. Ein Bugfix, der nicht in Git steht, ist kein Bugfix. Er ist eine Zeitbombe.

Composer Patches: der saubere Mittelweg

Das Plugin cweagans/composer-patches löst das Problem an der richtigen Stelle. Du legst deinen Fix als .patch-Datei ins Repository. Das Plugin spielt diesen Patch nach jedem composer install und composer update automatisch wieder ein, direkt nachdem das Paket entpackt wurde.

Installation:

composer require cweagans/composer-patches

Die Zuordnung von Paket zu Patch steht in der composer.json. Die Patch-Dateien selbst liegen üblicherweise in einem patches/-Ordner:

{
    "extra": {
        "patches": {
            "acme/pdf-generator": {
                "Null-Check beim Rendern leerer Seiten": "patches/pdf-generator-empty-page.patch"
            }
        }
    }
}

Beides – die composer.json-Zuordnung und die .patch-Datei – wird committet. Jeder im Team und jede CI-Umgebung bekommt den Fix damit automatisch.

Neu in 2.0: patches.json und ein Lockfile

Im Oktober 2025 ist cweagans/composer-patches 2.0 erschienen. Zwei Änderungen sind für Teams relevant.

Erstens eine eigene patches.json. Statt die Patch-Definitionen in der composer.json zu führen, kannst du sie in eine separate Datei auslegen. Das hält die composer.lock ruhig: Neue Patches lösen keine Änderung am Lockfile mehr aus.

Zweitens ein patches.lock.json. Diese Datei ist das Gegenstück zu Composers eigenem Lockfile. Sie hält für jeden angewendeten Patch eine SHA-256-Prüfsumme fest. Damit installiert jede Umgebung nachweislich denselben Patch-Stand. Ein still veränderter oder verrutschter Patch fällt auf, statt unbemerkt durchzurutschen.

Unter der Haube wendet 2.0 Patches jetzt über Git an, nicht mehr über das System-patch. Das Verhalten ist damit über Betriebssysteme hinweg gleich. Deine bestehende extra.patches-Konfiguration aus der 1.x-Zeit läuft weiter – der Umstieg ist rückwärtskompatibel.

Einen Patch erstellen

Ein Patch ist ein Unified Diff, also genau das, was git diff ausgibt. Du änderst die Datei im vendor/-Ordner und fängst die Änderung ab:

git diff --no-color vendor/acme/pdf-generator/src/Renderer.php > patches/pdf-generator-empty-page.patch

Danach machst du deine Änderung in vendor/ rückgängig und lässt composer install laufen. Der Patch wird sauber wieder eingespielt.

Bei mehreren Änderungen wird das Diffen von Hand mühsam. symplify/vendor-patches nimmt dir das ab: Du bearbeitest die Vendor-Dateien, ein Befehl scannt vendor/, schreibt die Diffs raus und trägt die Zuordnung in die composer.json ein.

composer require symplify/vendor-patches --dev
vendor/bin/vendor-patches generate

Die üblichen Stolperfallen

Patches sind versionsgebunden. Sie hängen an konkreten Zeilennummern und Umgebungen. Nach einem Paket-Update kann derselbe Patch schon nicht mehr passen.

ProblemUrsacheFix
Patch läuft nach Update nicht mehrZeilen im Paket haben sich verschoben"composer-exit-on-patch-failure": true setzen, damit der Composer-Lauf laut abbricht statt still weiterzumachen
Patch findet die Datei nichtFalscher -p/Path-Strip-Level; die a/- und b/-Präfixe im Diff passen nicht zum PfadDiff prüfen: relativ zum Paket-Root erzeugen, nicht zum Projekt-Root
Patch wird riesigDu baust eigentlich ein Feature ein, kein FixNicht patchen – siehe unten

Der wichtigste Schalter ist composer-exit-on-patch-failure. Ohne ihn schluckt Composer einen fehlgeschlagenen Patch und baut weiter. Dann hast du einen grünen Build ohne den Fix. Genau den Zustand, den du vermeiden wolltest.

Wann du nicht patchen solltest

Ein Patch ist für kleine Deltas gedacht: ein Null-Check, ein falscher Default, eine vergessene Escape-Funktion. Sobald du mehrere Methoden umschreibst oder Verhalten hinzufügst, ist der Patch das falsche Werkzeug.

Dann gehört der Fix stromaufwärts. Öffne einen Pull Request beim Paket. Bis der gemerged ist, überbrückt ein Patch die Wartezeit. Danach fliegt er wieder raus. Ein Patch ist eine Brücke, kein Zuhause.

Was du für dein Laravel-Projekt mitnimmst

  • halte Fixes an Fremdpaketen in Git, nicht in einem lokalen vendor/;
  • ein Patch bleibt ein kleines, klar umrissenes Delta;
  • nutze in neuen Projekten patches.json plus Lockfile für eine ruhige composer.lock;
  • setz composer-exit-on-patch-failure: true, damit veraltete Patches laut scheitern;
  • geh stromaufwärts und mach einen PR, sobald der Patch wächst.

Wenn dein Laravel-Projekt an solchen Stellen sauber bleiben soll, bauen wir Laravel-Projekte und individuelle Software, bei denen Paketpflege kein Zufall ist.

Häufige Fragen

Warum sollte man vendor/ nicht direkt bearbeiten?

Weil der Ordner Composer gehört, nicht dir. Er steht in der .gitignore, wird nicht committet und bei jedem composer install neu aus den Paketquellen aufgebaut. Eine Änderung dort lebt nur auf deinem Rechner. Im Team zieht die Kollegin den Branch ohne deinen Fix, die CI baut frisch ohne ihn – der Build ist grün, das Feature trotzdem kaputt. Ein Bugfix, der nicht in Git steht, ist keiner.

Wie patcht man ein Composer-Paket in Laravel sauber?

Mit dem Plugin cweagans/composer-patches. Du legst deinen Fix als .patch-Datei ins Repository (üblicherweise in einem patches/-Ordner) und trägst die Zuordnung von Paket zu Patch in der composer.json ein. Das Plugin spielt den Patch nach jedem composer install und composer update automatisch wieder ein, direkt nachdem das Paket entpackt wurde. Die .patch-Datei erzeugst du als Unified Diff, also mit git diff.

Was ist neu in cweagans/composer-patches 2.0?

Drei Dinge: eine eigene patches.json, sodass neue Patches keine Änderung an der composer.lock mehr auslösen; ein patches.lock.json, das je Patch eine SHA-256-Prüfsumme festhält, damit jede Umgebung nachweislich denselben Patch-Stand installiert; und die Anwendung über Git statt über das System-patch, wodurch das Verhalten über Betriebssysteme hinweg gleich ist. Bestehende extra.patches-Konfiguration läuft rückwärtskompatibel weiter.

Wann sollte man ein Paket nicht patchen?

Ein Patch ist für kleine Deltas gedacht – ein Null-Check, ein falscher Default, eine vergessene Escape-Funktion. Sobald du mehrere Methoden umschreibst oder Verhalten hinzufügst, ist der Patch das falsche Werkzeug. Dann gehört der Fix stromaufwärts als Pull Request beim Paket; ein Patch überbrückt nur die Wartezeit, bis der PR gemerged ist. Ein Patch ist eine Brücke, kein Zuhause.

Tobias Kokesch
Tobias Kokesch

Sr. Full-Stack Developer

Frontend & Backend. Entwickelt skalierbare Webanwendungen mit Fokus auf saubere Architektur und Wartbarkeit.