Composer : Résoudre un conflit Composer sans supprimer composer.lock
Quand Composer répond Your requirements could not be resolved to an installable set of packages, le problème n’est pas un verrou « cassé ». C’est une preuve : au moins deux règles du graphe ne peuvent pas être satisfaites ensemble. Supprimer composer.lock ferait disparaître votre point de comparaison et autoriserait Composer à recalculer bien plus de versions que nécessaire.
La bonne méthode consiste à conserver cet état reproductible, isoler la contrainte qui bloque, simuler la plus petite correction cohérente, puis valider le nouveau verrou. Le fil rouge ci-dessous est fictif : une application Symfony sous PHP 8.3, fixée sur une branche Symfony maintenue, veut ajouter vendor/package, mais vendor/bridge retient une dépendance transitive trop ancienne.
1. Préserver l’état reproductible
composer.json exprime des possibilités, le lock en choisit une
composer.json décrit les contraintes acceptées par le projet : par exemple PHP 8.3, une branche Symfony maintenue et une plage de versions pour chaque bibliothèque. composer.lock enregistre le graphe effectivement résolu, avec les versions exactes et leurs références. Quand le lock existe, composer install installe ces versions exactes ; composer update, lui, relance le solveur et réécrit le lock.
{
"require": {
"php": "^8.3",
"symfony/framework-bundle": "7.4.*",
"vendor/bridge": "^2.6"
},
"extra": {
"symfony": {
"require": "7.4.*"
}
}
}
Ces numéros sont un exemple, pas une prescription de branche Symfony. Dans le lock fictif, vendor/bridge vaut 2.6.4 et retient vendor/contracts en 1.9.2. Supprimer le lock obligerait Composer à refaire des choix pour tout le graphe autorisé par le JSON : Symfony, outils de test et dépendances indirectes pourraient bouger sans rapport avec l’ajout demandé.
Un extrait abrégé du composer.lock montre la différence : les versions ne sont plus des plages, mais les choix exacts du solveur.
{
"_readme": ["Extrait fictif abrégé"],
"content-hash": "…",
"packages": [
{
"name": "vendor/bridge",
"version": "2.6.4",
"require": {
"vendor/contracts": "^1.8"
}
},
{
"name": "vendor/contracts",
"version": "1.9.2"
}
]
}
Créer un point de retour avant toute commande mutante
Commencez sur une branche Git propre. Les commandes suivantes ne touchent pas aux dépendances ; elles enregistrent seulement un point de travail dans Git :
git status --short
git switch -c fix/composer-vendor-package
git diff -- composer.json composer.lock
Si le dépôt comporte déjà des modifications légitimes, commitez-les ou mettez-les de côté selon votre pratique d’équipe. Ne lancez pas une mise à jour globale par réflexe : sans liste de paquets, composer update autorise la réévaluation de toutes les dépendances racines dans leurs contraintes.
2. Lire l’erreur comme une chaîne de preuves
La sortie est longue parce que le solveur énumère les combinaisons rejetées. Cherchez la première chaîne qui relie votre demande à une contrainte incompatible, pas la première ligne contenant le mot « problem ».
$ composer require vendor/package:^3.0 --dry-run
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires vendor/package ^3.0.
# Contrainte racine demandée
- vendor/package 3.0.0 requires vendor/contracts ^2.0.
# Dépendance transitive nécessaire
- vendor/bridge 2.6.4 requires vendor/contracts ^1.8.
# Premier paquet réellement bloquant
- Only one version of vendor/contracts can be installed.
# Intersection vide entre ^1.8 et ^2.0
vendor/contracts est le point de collision, mais vendor/bridge est le parent à examiner : c’est lui qui impose l’ancienne branche. La correction ne consiste donc pas à forcer vendor/contracts 2.x ; il faut déterminer si une version compatible de vendor/bridge existe.
Reconnaître les autres familles de blocage
| Preuve dans la sortie | Cause à tester | Étape suivante |
|---|---|---|
requires php >=8.4 ou ext-intl * | PHP ou extension de plateforme | Comparer local, CI et production |
Root composer.json requires… | Contrainte racine trop stricte ou demande incompatible | Vérifier la compatibilité du projet avant de modifier le JSON |
A requires B, puis C requires B | Collision transitive | Interroger le parent A ou C |
does not match your minimum-stability | Version préstable filtrée | Décider explicitement si ce seul paquet peut être préstable |
conflicts ou replaces | Interdiction déclarée ou paquet substitué | Lire les métadonnées des versions concernées |
Une contrainte ^2.3 accepte les versions à partir de 2.3 sans passer en 3.0 ; ~2.3.1 reste sous 2.4 ; >=2.3 <3.0 écrit les bornes directement. conflict interdit une combinaison. replace signifie qu’un paquet affirme fournir le rôle d’un autre : vérifiez ce cas avant de conclure qu’une version « manque ».
3. Interroger le graphe avant de le modifier
Ces commandes sont informatives : elles ne réécrivent ni composer.json ni composer.lock.
Qui amène le paquet bloqué ?
composer why vendor/contracts --tree
composer why-not vendor/package 3.0.0 --tree
why (alias de depends) remonte les parents d’un paquet déjà présent dans le graphe. Avec --tree, la chaîne récursive permet de distinguer une dépendance directe d’une dépendance transitive. why-not (alias de prohibits) répond à une question plus précise : « qu’est-ce qui empêche exactement cette version ? »
vendor/bridge 2.6.4 requires vendor/contracts (^1.8)
vendor/package 3.0.0 requires vendor/contracts (^2.0)
Cette réponse désigne une incompatibilité, pas encore sa solution. Demandez ensuite quelles versions existent et quelles contraintes elles portent :
composer show vendor/bridge --all
composer show vendor/bridge 3.1.0
composer show vendor/package 3.0.0
show --all consulte les versions disponibles dans les dépôts configurés ; show paquet version affiche notamment ses exigences. Dans notre exemple fictif, vendor/bridge 3.1.0 accepte vendor/contracts ^2.0 et PHP ^8.2. Il devient un candidat, à condition que l’application soit compatible avec la branche 3.x du bridge.
Si why-not vendor/package 3.0.0 ne suffit pas, interrogez directement la cible de la collision avec composer why-not vendor/contracts 2.0.0 --tree. Le bon paquet à corriger est celui dont la contrainte crée l’intersection vide, pas nécessairement le dernier nom affiché.
4. Vérifier la plateforme réelle
Composer traite PHP et les extensions comme des paquets de plateforme. Un graphe peut être cohérent sur le poste du développeur et impossible en CI ou en production si les versions diffèrent.
php -v
php -m
composer show --platform
composer check-platform-reqs
Les trois premières commandes sont informatives. check-platform-reqs vérifie le PHP et les extensions réellement exécutés contre les paquets installés. Il ignore volontairement config.platform, justement pour ne pas confondre la cible simulée et la machine réelle.
{
"config": {
"platform": {
"php": "8.3.0",
"ext-intl": "8.3.0"
}
}
}
config.platform indique au solveur une plateforme cible fictive. C’est utile lorsqu’un poste local tourne déjà sous une version de PHP plus récente que la production. Ce n’est pas la preuve que l’extension existe réellement au déploiement. Si local est en PHP 8.4, CI en 8.3 et production en 8.3, résolvez pour la cible 8.3 puis exécutez composer check-platform-reqs dans chaque environnement de livraison.
Si l’erreur réclame PHP 8.4 alors que la production doit rester en 8.3, choisissez une version du paquet compatible avec 8.3 ou planifiez l’évolution de la plateforme. Si une extension manque, installez et activez l’extension dans l’environnement concerné. --ignore-platform-reqs ne corrige rien : il peut fabriquer un lock ou un répertoire vendor inexécutable et ne doit pas devenir une solution durable.
5. Choisir la correction minimale cohérente
Blocage par une contrainte racine
Si votre propre composer.json fixe vendor/bridge à ^2.6, ne remplacez pas cette contrainte par *. Consultez le journal de changements et les exigences de la branche 3.x, adaptez le code si nécessaire, puis exprimez seulement la plage réellement testée :
{
"require": {
"php": "^8.3",
"symfony/framework-bundle": "7.4.*",
"vendor/bridge": "^3.1",
"vendor/package": "^3.0"
}
}
Modifier ce JSON est une action mutante. Pour laisser Composer écrire proprement la nouvelle exigence sans résoudre immédiatement, l’équivalent en ligne de commande est composer require vendor/package:^3.0 --no-update ; cette commande modifie composer.json, mais pas le lock. Relisez ensuite le diff.
Blocage transitif
Si la contrainte racine autorise déjà la version compatible, faites une mise à jour partielle. Dans notre exemple, le parent bloquant doit évoluer avec le paquet demandé :
composer update vendor/package vendor/bridge --with-all-dependencies --minimal-changes --dry-run
--with-all-dependencies, abrégé -W, autorise aussi la mise à jour des dépendances héritées qui sont elles-mêmes des exigences racines. C’est parfois indispensable, mais cela élargit le solveur au-delà des deux noms tapés. --minimal-changes demande de conserver les versions verrouillées dès qu’elles restent compatibles ; cette option est disponible depuis Composer 2.7 et son comportement a été étendu dans les versions 2.x récentes. Vérifiez composer --version et composer update --help sur les installations plus anciennes.
Si aucune version maintenue de vendor/bridge ne supporte la nouvelle dépendance, la combinaison est impossible. Conservez une version compatible de vendor/package, remplacez le bridge abandonné ou adaptez l’application. N’éditez jamais composer.lock à la main : ses métadonnées et son hash doivent rester le résultat du solveur.
Blocage par la stabilité
Le réglage minimum-stability vaut stable par défaut. Si une correction indispensable n’existe qu’en version RC et que l’équipe accepte explicitement ce risque, limitez l’exception au paquet concerné, par exemple "vendor/package": "^3.1@RC", puis gardez "prefer-stable": true. Ne basculez pas tout le projet en dev pour faire taire le solveur.
De même, un alias de branche ou un dépôt personnalisé ne doit pas servir à déguiser une incompatibilité. Ces mécanismes ne sont justifiés que si vous contrôlez le code, sa version déclarée, sa provenance et son cycle de maintenance.
6. Vérifier ce que Symfony ajoute à la résolution
Symfony Flex est un plugin Composer. Il peut convertir un alias pratique en paquet réel, appliquer une recette et tenir à jour symfony.lock. Il ne rend pas compatibles deux contraintes qui ne le sont pas. Après un require, examinez donc aussi symfony.lock et les fichiers de recette modifiés, mais poursuivez le diagnostic dans le graphe Composer.
Dans un projet Flex, extra.symfony.require peut restreindre les composants Symfony à une branche précise. Si why-not montre que cette règle bloque la résolution, posez la vraie question : ajoutez-vous un paquet dans la branche Symfony actuelle, ou engagez-vous une montée de branche du framework ? Ce sont deux changements de portée différente.
{
"extra": {
"symfony": {
"require": "7.4.*"
}
}
}
Pour l’ajout fictif de vendor/package, conservez la branche maintenue déjà choisie si elle est compatible. Ne modifiez pas extra.symfony.require simplement parce qu’un composant pourrait être plus récent. Un changement de branche Symfony se prépare séparément : dépréciations, contraintes de tous les composants, tests et migration applicative.
7. Simuler, lire le plan, puis décider
--dry-run simule l’opération sans écrire les fichiers ni installer les paquets. C’est la commande qui transforme une hypothèse en plan de changement lisible :
composer update vendor/package vendor/bridge -W -m --dry-run
Lock file operations: 1 install, 2 updates, 0 removals
- Upgrading vendor/bridge (2.6.4 => 3.1.0)
- Upgrading vendor/contracts (1.9.2 => 2.2.1)
- Locking vendor/package (3.0.0)
Lisez les verbes : installing, upgrading, downgrading et removing. Ici, les trois opérations appartiennent à la chaîne prouvée. Si la simulation met à jour plusieurs composants Symfony, supprime un outil de test ou rétrograde un paquet sans rapport, arrêtez-vous. Revenez à why/why-not, ajoutez explicitement le vrai parent à la liste ou resserrez la contrainte compatible.
Une fois le plan accepté, retirez uniquement --dry-run. La commande suivante est mutante : elle met à jour composer.lock, installe les versions résolues dans vendor et peut exécuter les scripts autorisés du projet.
composer update vendor/package vendor/bridge -W -m
Relisez immédiatement les changements :
git diff -- composer.json composer.lock symfony.lock
git status --short
Si le périmètre n’est pas acceptable, revenez aux fichiers suivis puis restaurez vendor depuis l’ancien lock :
git restore -- composer.json composer.lock symfony.lock
composer install
Adaptez la liste si le projet n’utilise pas Flex ou si symfony.lock n’est pas suivi. git restore efface les modifications locales non commitées de ces fichiers : d’où l’importance de partir d’un état propre ou d’avoir créé un commit de sauvegarde.
8. Valider le nouveau lock comme un livrable
La résolution n’est terminée que si le projet peut être reconstruit à partir du lock obtenu. Commencez par les contrôles Composer :
composer validate --strict
composer check-platform-reqs
validate vérifie le schéma de composer.json et la synchronisation de composer.lock. check-platform-reqs est informatif et doit aussi tourner dans l’environnement de déploiement, où PHP et les extensions réelles comptent.
Dans un répertoire de travail propre ou un job CI, récupérez le commit candidat et exécutez composer install. Avec le lock présent, cette commande installe le graphe exact au lieu de choisir de nouvelles versions. Lancez ensuite la suite de tests du projet, par exemple :
composer install
php bin/phpunit
composer check-platform-reqs
Examinez le diff du lock : les paquets ajoutés, mis à jour, rétrogradés ou supprimés doivent correspondre au plan validé. Committez ensemble composer.json et composer.lock, ainsi que symfony.lock si une recette Flex a changé :
git add composer.json composer.lock symfony.lock
git commit -m "Fix Composer dependency resolution for vendor/package"
Le JSON dit quelles versions seraient acceptables lors d’une future résolution. Seul le lock enregistre celles qui ont réellement passé vos tests aujourd’hui. C’est pourquoi collègues et CI obtiennent le même graphe avec composer install.
9. L’arbre de décision en une lecture
- PHP ou extension bloque : corrigez l’environnement, ou la cible
config.platformsi elle décrit mal la production ; ne masquez pas l’exigence. - Une dépendance racine bloque : vérifiez la compatibilité de la version suivante, adaptez le code, puis corrigez précisément
composer.json. - Une dépendance transitive bloque : remontez au parent avec
why --tree, confirmez la version cible avecwhy-notet effectuez une mise à jour partielle. - La stabilité bloque : autorisez au besoin une stabilité explicite pour un seul paquet ; ne placez pas tout le projet en
dev. - La simulation dépasse le périmètre : n’appliquez rien ; réduisez la liste ou reconsidérez la contrainte racine.
Conclusion
Un conflit Composer se résout en réduisant l’incertitude : préserver le lock, lire la première chaîne incompatible, interroger le graphe, vérifier la plateforme, puis simuler une mise à jour ciblée. Si la simulation reste dans le périmètre attendu et que l’installation propre passe les tests, le nouveau composer.lock devient une preuve reproductible — pas un fichier jetable.
FAQ
Les réponses courtes aux réflexes les plus fréquents face au solveur Composer.
01
Puis-je régénérer le lock si le conflit semble incompréhensible ?
Non par défaut. Cela réévalue tout le graphe autorisé et supprime votre référence. Gardez le lock, utilisez why et why-not, puis mettez à jour une liste ciblée.
02
-W met-il tout le projet à jour ?
Pas comme une mise à jour globale, mais il autorise aussi les dépendances héritées, y compris celles qui sont racines. Contrôlez toujours son plan avec --dry-run et --minimal-changes.
03
Pourquoi check-platform-reqs contredit-il parfois la résolution ?
La résolution peut utiliser la plateforme simulée par config.platform. check-platform-reqs ignore cette simulation et vérifie le PHP et les extensions réellement chargés.
04
Faut-il committer composer.lock pour une application ?
Oui. Il fixe les versions exactes installées par les collègues, la CI et le déploiement. Committez-le avec le JSON qui a produit cette résolution.
Partager cet article
Écrit par
larevuegeek
Commentaires (0)