Composer/Replace: Unterschied zwischen den Versionen
Aus Contao Community Documentation
Tril (Diskussion | Beiträge) (→TLDR) |
Tril (Diskussion | Beiträge) (→TLDR) |
||
Zeile 121: | Zeile 121: | ||
Ein <code>{ "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "*" } }</code> sorgt dafür, dass jede Version des alten Paketes, durch das neue ersetzt wird. Das bedeutet aber auch, dass eine potentiell inkompatible Version 3 von <code>me/my-extension</code> installiert wird, auch wenn mit <code>{ "require": { "contao-legacy/my-extension": "1.0" } }</code> die Version 1 von <code>contao-legacy/my-extension</code> angefordert wird. Das schlimmste: Als Benutzer kann man (fast) nichts dagegen machen! | Ein <code>{ "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "*" } }</code> sorgt dafür, dass jede Version des alten Paketes, durch das neue ersetzt wird. Das bedeutet aber auch, dass eine potentiell inkompatible Version 3 von <code>me/my-extension</code> installiert wird, auch wenn mit <code>{ "require": { "contao-legacy/my-extension": "1.0" } }</code> die Version 1 von <code>contao-legacy/my-extension</code> angefordert wird. Das schlimmste: Als Benutzer kann man (fast) nichts dagegen machen! | ||
− | Vereinfacht ausgedrückt ist ein replace nichts weiter, als ein Alias auf das | + | Vereinfacht ausgedrückt ist ein replace nichts weiter, als ein Alias auf das Paket in dem der replace definiert wurde: |
<pre> | <pre> | ||
Zeile 128: | Zeile 128: | ||
test/old * alias for test/new 2.0 | test/old * alias for test/new 2.0 | ||
</pre> | </pre> | ||
+ | |||
Deshalb sollte immer <code>{ "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "self.version" } }</code> verwendet werden! | Deshalb sollte immer <code>{ "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "self.version" } }</code> verwendet werden! |
Version vom 22. Mai 2014, 17:11 Uhr
Unvollständiger Artikel: dieser Artikel ist noch nicht sauber bearbeitet.
Bitte erweitere ihn und entferne erst anschliessend diesen Hinweis. |
Inhaltsverzeichnis
Wie composer mit replace
umgeht
Mit der replace
Eigenschaft kann man festlegen, dass das eigene Paket ein fremdes Paket "ersetzt". Dafür kann es mehrere Gründe geben, der häufigste ist wohl, dass das Paket unter einem anderen Namen weiter entwickelt wird, weil bspw. der Entwickler gewechselt hat. Im Contao Kontext sieht man auch sehr viel { "replace": { "contao-legacy/my-extension": "..." } }
. Das sind replaces für Pakete, die über den Legacy ER2 Server in Composer bereitgestellt werden, vom Entwickler dann aber später als "echtes" Composer Paket weiter geführt werden.
Sehr weit verbreitet ist vor allem die Notation mit *
:
{ "replace": { "contao-legacy/my-extension": "*" } }
Im folgenden soll erläutert werden, warum ein replace auf alle Versionen wie in dem vorhergehenden Beispiel die denkbar schlechteste Variante ist, die man sich vorstellen kann. Dazu muss man erst ein mal verstehen, wie replace funktioniert.
Wir gehen einfach mal davon aus, wir haben die folgenden 3 Pakete:
{ "name": "test/old", "version": "1.0", "dist": { "url": "...", "type": "zip" }, "source": { "url": "...", "type": "svn", "reference": "master" } }
{ "name": "test/old", "version": "1.1", "dist": { "url": "...", "type": "zip" }, "source": { "url": "...", "type": "svn", "reference": "master" } }
{ "name": "test/new", "version": "2.0", "dist": { "url": "...", "type": "zip" }, "source": { "url": "...", "type": "svn", "reference": "master" }, "replace": { "test/old": "*" } }
Um die gültigen Pakete zu finden, arbeitet Composer intern mit einem Pool. In diesen Pool generiert Composer für jeden Paketnamen eine sortierte Liste der Pakete erzeugt, die für den Paketnamen gültig sind.
Für das Paket test/old
sieht diese Liste folgend aus:
- test/old 1.0
- test/old 1.1
- test/new 2.0
Wie Composer darauf kommt, dass test/new
in diese Liste kommt ist ganz einfach. Jedes Paket verfügt nicht nur über seinen eindeutigen Namen, sondern auch über Alias-Namen. Schaut man sich die BasePackage::getNames() Methode an, wird schnell klar welche Namen für ein Paket akzeptiert werden. Abgesehen vom Package::$name
werden auch noch alle Package::$provides
und Package::$replaces
als gültige Namen für das Paket betrachtet.
Unter Strich heißt dass, dass die Namensliste von test/new
aus [test/new, test/old]
besteht und test/new
deshalb in der Paketliste zu test/old
(2 Absätze zuvor) auftaucht.
Wir gehen davon aus, wir wollen mit { "require": { "test/old": "~1.0" } }
das Paket test/old
in irgendeiner 1er Version installieren (Hinweis: ~1.0
ist gleichbedeutend mit >=1,<2-dev
).
Betrachtet man jetzt die Auswertungslogik in der Pool::computeWhatProvides() Methode welche die Paketliste durchgeht und das höchst-versionierte (letzte) gültige Paket auswählt, kommt man zu folgendem Ergebnis:
- Paket
test/old
in Version 1.0 matched über seinen Namen und seine Version - Paket
test/old
in Version 1.0 matched ebenfalls über seinen Namen und seine Version - Paket
test/new
in Version 2.0 matched über sein replace, außerdem matched die Version*
auf die Constraint~1.0
Damit wird test/new
als gültiges Paket akzeptiert und weil es die jüngste Versionsnummer hat, wird dieses auch installiert.
Intern macht Composer also innerhalb der Pool::match() Methode eine Prüfung, ob die Version *
auf die Constraint ~1.0
zutrifft. Und weil *
immer zutrifft, ist es also völlig egal was man als Constraint auswählt. Dem Nutzer wird quasi jede Chance genommen, das Paket test/old
in einer von ihm vorgegebenen Version zu installieren!
Lösung: Anstelle des Wildcards, verwendet man einfach { "replace": { "contao-legacy/my-extension": "self.version" } }
!
{ "name": "test/new", "version": "2.0", "dist": { "url": "...", "type": "zip" }, "source": { "url": "...", "type": "svn", "reference": "master" }, "replace": { "test/old": "self.version" } }
Die Constraint self.version
wird intern durch die Version des ersetzenden Pakets ersetzt. In Pool::match() wird jetzt also geprüft ob die Version 2.0
auf die Constraint ~1.0
zutrifft, was natürlich fehl schlägt.
Hätten wir jetzt allerdings ein Paket test/new
in Version 1.2, welches als Nachfolger von test/old
Version 1.1 fungiert, wäre wieder alles in Ordnung, weil die Version 1.2
auf die Constraint ~1.0
matched. Im Regelfall ist es genau dass, was wir wollen!
TLDR
Ein { "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "*" } }
sorgt dafür, dass jede Version des alten Paketes, durch das neue ersetzt wird. Das bedeutet aber auch, dass eine potentiell inkompatible Version 3 von me/my-extension
installiert wird, auch wenn mit { "require": { "contao-legacy/my-extension": "1.0" } }
die Version 1 von contao-legacy/my-extension
angefordert wird. Das schlimmste: Als Benutzer kann man (fast) nichts dagegen machen!
Vereinfacht ausgedrückt ist ein replace nichts weiter, als ein Alias auf das Paket in dem der replace definiert wurde:
test/old 1.0 test/old 1.1 test/old * alias for test/new 2.0
Deshalb sollte immer { "name": "me/my-extension", "replace": { "contao-legacy/my-extension": "self.version" } }
verwendet werden!
Sonderfälle
Natürlich gibt es Sonderfälle, diese bedingen aber in der Regel die Angabe einer expliziten Version bspw. { "replace": { "contao-legacy/my-extension": "1.1" } }
.
In diesem Fall muss der Entwickler darauf achten, seinen replace Eintrag akribisch mit zu führen, bei steigenden Versionsnummern.
Von der Verwendung von Wildcards, auch wenn es nur eine Major- oder Release-Wildcard bspw. { "replace": { "contao-legacy/my-extension": "1.1.*" } }
ist, wird grundsätzlich abgeraten!