composer.json - Versionsbedingungen und Abhängigkeiten verstehen

Wenn du dich schon länger mit Contao beschäftigst, wirst du früher oder später mit der composer.json-Datei in Berührung kommen. In diesem Blogbeitrag gehe ich etwas genauer auf die composer.json ein. Ich erkläre dir das Thema Versionsbedingungen und gebe Tipps, wie du mit Fehlermeldungen umgehst, wenn bestimmte Versionen nicht installiert werden können.

Der folgende Beitrag erhebt keinen Anspruch auf Vollständigkeit und Ausführung aller Möglichkeiten. Dazu ist das Thema zu umfangreich. Es geht vielmehr darum, dir einen Überblick zu verschaffen und ein besseres Verständnis zu vermitteln. Mehr über Composer findest du in der offiziellen Dokumentation.

Contao, Pakete und die Welt von Composer

Zu Zeiten von Contao 3 konntest du Contao einfach als ein komplettes ZIP-Paket herunterladen und auf deinen Server übertragen. Dies hat sich mit dem Release von Contao 4.0 im Jahr 2015 grundlegend geändert. Seitdem basiert Contao auf dem Symfony-Framework und benötigt Composer zur Paketverwaltung. So besteht Contao 5 aus knapp 190 einzelner Pakete. Wow, eine ganze Menge!

Damit nun Composer weiss, welche Pakete benötigt werden, gibt es eine Konfigurationsdatei, die composer.json. In dieser Datei wird unter anderem festgelegt, welche Contao-Version, Pakete und Abhängigkeiten für die Installation benötigt werden.

Daraus resultiert auch, dass nahezu jede Contao-Installation eine einzigartige Kombination aus Paketen und Versionen ist. Selbst wenn zwei Installationen dieselbe Contao-Version installiert haben, sind noch lange nicht alle Pakete identisch. Das liegt daran, dass Composer immer versucht, zum jeweiligen Ausführungszeitpunkt die bestmögliche Paketkombination zu finden. In diese bestmögliche Paketkombination fließt auch deine Plattform mit ein. Also eingesetzte PHP-Version, PHP-Erweiterungen etc.

Den aktuell installierten Stand mit exakten Versionen der Pakete inklusive derer Abhängigkeiten findest du in der composer.lock-Datei.

Die composer.lock wird automatisch von Composer erstellt und darf niemals von Hand verändert werden.

Um das Ganze etwas greifbarer zu machen, hier mal ein Vergleich zum Kuchenbacken.

Im Rezept (composer.json) stehen die verschiedenen Zutaten (Pakete) und die erlaubten Mengen (Versionen), um einen Apfelkuchen (Contao 5) zu backen. Bei den Äpfeln wird als Menge z. B. 5 - 6 große Äpfel (Versionsbedingung) angegeben. Bevor wir mit dem Backen beginnen, sammeln wir jetzt alle Zutaten (composer update) zusammen und bereiten diese vor. In unserer Schüssel haben wir jetzt z. B. exakt 1042 Gramm geschnittene Äpfel. Die exakte Menge aller Zutaten (composer.lock) liegen nun für den nächsten Schritt bereit. Jetzt fügen wir die Zutaten zusammen und backen (composer install) unseren Kuchen (Contao 5).

Wenn jetzt meine Mutter und ich den gleichen Kuchen backen (Contao 5.2.7), dann kommt höchstwahrscheinlich ein anderes Ergebnis raus. Das liegt schon allein daran, dass ich einen anderen Backofen (Plattform) habe. Zudem kommt, dass nach dem Schneiden der Äpfel bei mir statt 1024 Gramm nur 1012 Gramm übrig bleiben. Am Ende haben wir zwar beide einen leckeren Apfelkuchen gebacken, doch schmecken werden beide etwas anders.

Und genau so ist es mit Contao und Composer. Am Ende haben wir nach der Installation beide Contao 5.2.7 installiert, doch es werden höchstwahrscheinlich nicht die exakt gleichen Versionen aller Pakete installiert sein.

Aufbau der composer.json-Datei

Kommen wir nun zum Aufbau der composer.json. Als Beispiel nehmen wir die composer.json von Contao 5.x.

Du findest die Datei im Root-Verzeichnis von Contao. Sie enthält diverse Informationen wie den Namen des Projekts, eine Beschreibung, die Autoren, den Lizenztyp sowie eine Liste der benötigten Pakete. Ferner gibt es dann noch weitere Einträge wie conflict, extra, scripts und mehr, auf die wir hier nicht eingehen wollen. Weitere Details über die einzelnen Parameter des composer.json-Schemas findest du auf der Website von Composer.

Wir möchten uns hauptsächlich mit den zu installierenden Paketen beschäftigen.

Definition der zu installierenden Pakete

Dazu wird der Abschnitt «require» verwendet. Hier findest du eine Liste von Paketen, die für unsere Installation genutzt werden. Die Pakete werden als Schlüssel-Wert-Paare angegeben, wobei der Name des Pakets der Schlüssel ist und die Version bzw. Versionsbedingung als Wert angegeben wird. Bei Contao 5.3 sieht der require-Teil zum Beispiel so aus:

"require": {
    "contao/calendar-bundle": "^5.3",
    "contao/comments-bundle": "^5.3",
    "contao/conflicts": "@dev",
    "contao/faq-bundle": "^5.3",
    "contao/listing-bundle": "^5.3",
    "contao/manager-bundle": "5.3.*",
    "contao/news-bundle": "^5.3",
    "contao/newsletter-bundle": "^5.3"
},

Da wir aktuell keine Erweiterung nutzen, siehst du nur die Contao-Bundles, das Manager-Bundle und das conflicts-Bundle in der Liste. Die Funktion der contao/conflicts hat Yanick bereits ausführlich in folgendem Beitrag erklärt.

In unserem Beispiel soll also jetzt Contao 5.3 installiert werden, sowie die entsprechenden Bundles für Kalender, Kommentare, FAQ, Auflistung, News und Newsletter. Wenn du die Versionsnummer etwas genauer ansiehst, dann steht zusätzlich zur Versionsnummer noch ein zusätzliches Zeichen. Beim calendar-bundle steht z. B. das Caret-Symbol ^ und hinter der Version des manager-bundles ein Wildcard *. Damit sind wir auch schon beim Thema Versionsbedingungen.

Versionsbedingungen in der composer.json

Diese Bedingungen legen fest, welche Versionen eines Pakets installiert werden können und welche nicht. Composer versucht dabei immer die aktuellsten Pakete zu installieren. Es gibt verschiedene Möglichkeiten, die Versionen zu definieren. Eine Übersicht aller Möglichkeiten findest du in der Composer-Doku.

Am häufigsten wirst du eine der folgenden Bedingungen sehen:

  • Exakt: 1.2.5
    Bedeutet, es darf nur exakt die Version 1.2.5 installiert werden.
  • Größer oder gleich: >=1.2
    Bedeutet, alles was höher oder gleich Version 1.2 ist - aber Vorsicht, auch Version 2.0 oder 3.0 sind größer als 1.2!
  • Wildcard: 1.2.*
    Bedeutet, die Stelle mit dem Stern darf sich ändern. Also alle 1.2.x-Versionen, jedoch nicht 1.3.0
  • Caret Version Range ^1.3
    Es bedeutet, alle Versionen größer 1.3, jedoch nicht Version 2.0. Somit sind alle Non-Breaking-Versionen bis zur nächsten Major-Version erlaubt.
  • Logisches ODER: ^4.13 || ^5.0
    Bedeutet, alle Versionen größer 4.13 jedoch nicht Version 5.0 ODER alle Versionen größer 5.0 jedoch nicht Version 6.0

Sehen wir uns mit diesem Wissen noch mal die Versionsbedingungen in der composer.json von Contao 5.3 an:

"contao/calendar-bundle": "^5.3", // alles >=5.3.0, jedoch <6.0.0

"contao/manager-bundle": "5.3.*", // alle Versionen 5.3.x jedoch nicht 5.4

Das war jedoch bislang nicht alles. Denn jedes dieser Pakete hat nun selbst wiederum eine composer.json.

So sieht die composer.json des manager-bundle wie folgt aus:

"require": {
    "php": "^8.1",
    "ext-json": "*",
    "contao/core-bundle": "self.version",
    "contao/manager-plugin": "^2.4",
    "doctrine/dbal": "^3.6",
    "doctrine/doctrine-bundle": "^2.8",
    "friendsofsymfony/http-cache": "^2.6",
    "friendsofsymfony/http-cache-bundle": "^2.6",
    "nelmio/cors-bundle": "^2.0.1",
    "nelmio/security-bundle": "^2.2",
    "symfony/cache": "^6.4",
    "symfony/config": "^6.4",
    "symfony/console": "^6.4",
    "symfony/debug-bundle": "^6.4",
    "symfony/dependency-injection": "^6.4",
    "symfony/deprecation-contracts": "^3.0",
    "symfony/doctrine-bridge": "^6.4",
    "symfony/dotenv": "^6.4",
    "symfony/error-handler": "^6.4",
    "symfony/expression-language": "^6.4",
    "symfony/filesystem": "^6.4",
    "symfony/finder": "^6.4",
    "symfony/framework-bundle": "^6.4",
    "symfony/http-client": "^6.4",
    "symfony/http-foundation": "^6.4",
    "symfony/http-kernel": "^6.4",
    "symfony/mailer": "^6.4",
    "symfony/monolog-bridge": "^6.4",
    "symfony/monolog-bundle": "^3.1",
    "symfony/process": "^6.4",
    "symfony/routing": "^6.4",
    "symfony/security-bundle": "^6.4",
    "symfony/stopwatch": "^6.4",
    "symfony/twig-bundle": "^6.4",
    "symfony/web-profiler-bundle": "^6.4",
    "symfony/yaml": "^6.4",
    "toflar/psr6-symfony-http-cache-store": "^4.0",
    "twig/extra-bundle": "^3.0"
},

Wie du hier siehst, sind zahlreiche weitere Abhängigkeiten (PHP-Version, Contao-Core, Symfony, Twig, etc.) definiert, die wir auf den ersten Blick gar nicht in unserer Root-composer.json von Contao gesehen haben.

Wenn wir jetzt noch mal weitergehen, dann hat das Paket symfony/framework-bundle wiederum eine composer.json. Hier sieht der require-Teil dann wie folgt aus:

"require": {
    "php": ">=8.1",
    "composer-runtime-api": ">=2.1",
    "ext-xml": "*",
    "symfony/cache": "^5.4|^6.0|^7.0",
    "symfony/config": "^6.1|^7.0",
    "symfony/dependency-injection": "^6.4|^7.0",
    "symfony/deprecation-contracts": "^2.5|^3",
    "symfony/error-handler": "^6.1|^7.0",
    "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
    "symfony/http-foundation": "^6.4|^7.0",
    "symfony/http-kernel": "^6.4",
    "symfony/polyfill-mbstring": "~1.0",
    "symfony/filesystem": "^5.4|^6.0|^7.0",
    "symfony/finder": "^5.4|^6.0|^7.0",
    "symfony/routing": "^6.4|^7.0"
},

Spätestens jetzt verstehst du, warum das Auflösen der Paketabhängigkeiten sehr komplex ist. Zu den gefundenen Abhängigkeiten kommt noch die auf dem Server vorhandene PHP-Version und andere Servereigenschaften. Ausserdem müssen auch entsprechende Conflict-Einträge beachtet werden. Zum Glück haben wir Composer, der für uns die Auflösung übernimmt.

Das bedeutet jedoch auch, dass es sein kann, dass wir gerne eine bestimmte Erweiterung installieren oder aktualisieren möchten, jedoch aufgrund von anderer Abhängigkeiten, und davon gibt es ja sehr viele, nicht möglich ist.

Hilfreiche Kommandos und Tipps zur Fehlersuche

Um dir die Arbeit und die Fehlersuche zu erleichtern, gebe ich dir in folgendem Absatz noch ein paar Tipps an die Hand, wie du Fehlermeldungen besser verstehen kannst. Zusätzlich zeige ich dir drei Befehle für Composer, die dir in dem Zusammenhang helfen können.

Beispiel 1 : Abhängigkeiten herausfinden

Nehmen wir an, du hast festgestellt, dass die Erweiterung Notification-Center (terminal42/notification_center) installiert ist, obwohl du diese gar nicht in der Liste der Pakete im Contao Manager siehst. In deiner root-composer.json findest du ebenfalls keinen Eintrag dazu.

Um nun herauszufinden, warum das Notification Center installiert ist, gibt es mehrere Möglichkeiten.

Du kannst z. B. den Contao Manager starten, dir das Paket Notification Center suchen und dort dann auf den Reiter «Abhängige» klicken.

Ausserdem kannst du über die Konsole folgenden Befehl ausführen.

php composer.phar depends terminal42/notification_center

Das Ergebnis könnte dann wie folgt aussehen:

isotope/isotope-core 2.8.17 requires terminal42/notification_center (^1.0)
terminal42/notification_center 1.6.14 replaces contao-legacy/notification_center (self.version)

Du siehst, dass die Erweiterung isotope/isotope-core als Abhängigkeit das Paket terminal42/notification_center definiert hat und deswegen installiert wurde.

Wenn du den ganzen Baum der Abhängigkeiten sehen willst, dann verwendest du zusätzlich noch den Parameter -t.

Das sieht dann so aus:

php composer.phar depends -t terminal42/notification_center

Die Liste kann natürlich auch wesentlich umfangreicher werden. Nehmen wir an, wir wollen alle Abhängigkeiten von twig/twig herausfinden. Dann sieht das Ergebnis wie folgt aus:

php composer.phar depends twig/twig

Wenn du dir das in der Baumansicht ausgeben lässt, dann siehst du ein Ergebnis, das mehr als 1260 Zeilen lang ist.

Beispiel 2 : Eine Erweiterung lässt sich unter Contao 5 nicht installieren

Zum aktuellen Zeitpunkt dieses Blogbeitrags ist das Notification Center noch nicht mit Contao 5 kompatibel. Wenn du eine Installation über den Contao Manager startest, erhältst du eine Fehlermeldung wie diese:

> Resolving dependencies using Composer Cloud v3.6.0-1-g9d9036c
[6.8MiB/0.14s] Loading composer repositories with package information
[71.9MiB/11.66s] Updating dependencies
[100.6MiB/12.09s] Your requirements could not be resolved to an installable set of packages.
[100.6MiB/12.09s]
  Problem 1
    - terminal42/notification_center[1.7.0, ..., 1.7.3] require contao/core-bundle ^4.13 -> found contao/core-bundle[4.13.0, ..., 4.13.35] but these were not loaded, likely because it conflicts with another require.
    - Root composer.json requires terminal42/notification_center ^1.7 -> satisfiable by terminal42/notification_center[1.7.0, 1.7.1, 1.7.2, 1.7.3].
[100.6MiB/12.09s] <warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>
[39.8MiB/12.13s] Memory usage: 39.76MB (peak: 202.63MB), time: 12.14s.
[39.8MiB/12.13s] Finished Composer Cloud resolving.

Der Grund, warum das notification_center nicht installiert wird, steht in dieser Zeile:

terminal42/notification_center[1.7.0, ..., 1.7.3] require contao/core-bundle ^4.13 -> found contao/core-bundle[4.13.0, ..., 4.13.35] but these were not loaded, likely because it conflicts with another require.

Composer sagt dir, dass das Paket notification_center Contao ^4.13 benötigt, es jedoch nicht installieren kann, weil es einen Konflikt mit einem anderen benötigten Paket gibt.

In unserem Fall haben wir in unserer root-composer.json festgelegt, dass wir Contao ^5.2 haben wollen und kein Contao 4.13 zugelassen ist.

Wenn du ein Problem mit der Installation einer Erweiterung hast, dann ist es sehr hilfreich, wenn du dir die Abhängigkeiten ansiehst. Das kannst du z. B. im Manager über den Reiter «Abhängigkeiten» machen, bei Packagist oder direkt in der composer.json der Erweiterung.

Hier siehst du, dass Contao Core mit der Versionsbedingung ^4.13 benötigt wird und somit kein Contao 5 erlaubt ist.

Das Popup im Contao Manager zeigt immer nur die Informationen der neuesten Version an. Genauer sieht man das bei packagist.org, dort lässt sich das für jede Version wählen.

Beispiel 3 : Die aktuellste Version einer Erweiterung wird nicht installiert

Nehmen wir an, du nutzt aktuell Contao 4.9 und PHP 7.4 und hast die Erweiterung contao/page_image in der Version 4.1. installiert.

Du hast jetzt im Contao Manager gesehen, dass es bereits Version 4.2.1 gibt. Du führst also ein Update aller Pakete aus, doch die Version 4.2.1 wird nicht installiert. Es bleibt weiter die Version 4.1 installiert.

Du kannst nun wiederum selbst über die «Abhängigkeiten» in der composer.json herauslesen, wo das Problem liegt oder du verwendest den Befehl:

php composer.phar prohibits terminal42/contao-pageimage 4.2.1

Die Antwort sieht dann wie folgt aus:

terminal42/contao-pageimage 4.2.1      requires         php (^8.1 but 7.4.33 is installed)           
terminal42/contao-pageimage 4.2.1      requires         contao/core-bundle (^4.13 || ^5.0)           
contao/managed-edition      dev-master does not require contao/core-bundle (but 4.9.42 is installed)

Die Ursache sind in diesem Fall sogar zwei Abhängigkeiten. Es wird PHP ^8.1 benötigt, es ist aber 7.4 installiert. Außerdem wird Contao ^4.13 oder ^5.0 benötigt, wir nutzen jedoch Contao 4.9.

Du kannst die neueste Version der Erweiterung erst installieren, wenn beide Abhängigkeiten erfüllt sind.

Beispiel 4 : Alle Installationen mit einer veralteten Erweiterung finden

Die vorherigen Beispiele beziehen sich alle auf eine einzelne Installation. Was aber, wenn du wissen möchtest, bei welchen Installationen noch nicht contao-pageimage 4.2.1 installiert ist? In diesem Fall kann dir trakked helfen.

Mithilfe des Paket-Filters und einer Versionsbedingung kannst du gezielt danach suchen. Dazu genügt es, im Paket-Filter die Erweiterung terminal42/contao-pageimage einzuschränken und zusätzlich die Versionsbedingung <4.2 einzugeben. Nun bekommst du eine Liste mit allen betroffenen Installationen.

Fazit

Ich könnte sicher noch zahlreiche weitere Beispiele aufführen, doch das würde den Rahmen hier sprengen.

Ich hoffe, dieser Blogbeitrag hat dir geholfen, das Thema Versionierung besser zu verstehen und dich motiviert, etwas tiefer mit den Hintergründen der composer.json zu befassen und deine Kenntnisse weiter auszubauen.

Kommentare

Kommentar von Holger Hägele |

Danke, Christian, für diesen sehr nützlichen Beitrag!
Gruß aus Ludwigsburg

Antwort von Christian Feneberg

Danke für dein Feedback.

Kommentar von Bernhard Renner |

Wie immer - ein einfach und kurzweilig erklärter Informationsbeitrag.
vielen Dank dafür!

Antwort von Christian Feneberg

Vielen Dank, Bernhard.

Kommentar von Dieter Haskamp |

Guter Artikel, das mit dem Rezept hat mir sehr gut gefallen. ;)

Antwort von Christian Feneberg

Vielen Dank, Dieter.

Einen Kommentar schreiben

Bitte addieren Sie 5 und 2.