Tu lis « Laravel 13.3 supporte PHP 8.3 », tu respires, tu merges… et là, au premier composer require un peu sérieux, tu te prends un mur : « votre plateforme PHP doit être >= 8.4 ». Le genre de message qui donne l’impression d’avoir raté un truc évident.
Le piège est simple mais vicieux : Laravel peut rester compatible PHP 8.3 tout en laissant Composer résoudre une dépendance transitive (souvent Symfony) qui, elle, exige PHP 8.4. Et Composer ne « choisit pas une version sympa ». Il choisit la version qui matche les contraintes. Si tu lui laisses la porte ouverte, il passe.
Le cas réel : Laravel 13.3+ peut te faire résoudre Symfony 8 (et Symfony 8 demande PHP 8.4)
Ce qui déclenche l’embrouille, c’est une combinaison très classique dans l’écosystème PHP : un package (ici Laravel) autorise plusieurs branches d’une dépendance (par exemple ^7 || ^8 côté Symfony), et Composer, quand tu mets à jour, va naturellement préférer la plus récente qui respecte la contrainte. Si Symfony 8 est autorisé, Composer essaie Symfony 8.
Sauf que Symfony 8 a relevé son plancher. Résultat : tu peux être dans un projet encore en PHP 8.3, sans avoir « demandé » Symfony 8 explicitement, et Composer te dit quand même « PHP 8.4 minimum », parce qu’il a trouvé une solution… mais elle n’est pas compatible avec ta plateforme.
Le symptôme le plus fréquent, c’est : « ça marchait hier, et aujourd’hui, un nouveau composer update ou l’ajout d’un package déclenche la contrainte PHP >= 8.4 ». En réalité, ça ne sort pas de nulle part. Tu avais juste un lockfile qui te maintenait sur Symfony 7.x (ou sur une combinaison compatible), et tu viens d’ouvrir la boîte de Pandore en autorisant une nouvelle résolution.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Pourquoi Composer “t’impose” PHP 8.4 alors que Laravel annonce PHP 8.3
Il faut se mettre d’accord sur un point : « Laravel supporte PHP 8.3 » veut dire « il existe une résolution de dépendances valide en PHP 8.3 ». Ça ne veut pas dire « n’importe quelle résolution que Composer choisira sera compatible PHP 8.3 ».
Composer ne lit pas une phrase de blog, ni une compat marketing. Il lit ton composer.json, les composer.json des dépendances, et ton lockfile. Si la contrainte dit « Symfony 7 ou 8 », alors Symfony 8 est un candidat. Et si ton lockfile est absent, pas à jour, ou si tu demandes une mise à jour qui force un recalcul (typiquement composer update symfony/*, un composer require qui entraîne un update large, ou un conflit qui oblige à remonter des versions), Composer essaye de remonter au plus haut.
Le détail qui rend ça sournois en équipe, c’est que ce n’est pas forcément reproductible selon l’état du lockfile et la façon dont chacun installe. Un collègue peut avoir une résolution encore compatible (lockfile ancien, cache, contraintes déjà fixées), et toi tu tombes sur une nouvelle résolution qui part sur Symfony 8. D’où la sensation de bug fantôme.
Diagnostiquer proprement : prouver quelle dépendance demande PHP 8.4
Quand Composer te sort « requires php >= 8.4 », ne pars pas en croisade contre Laravel. Commence par faire parler le solver. L’objectif est juste d’identifier le premier package dans l’arbre qui impose ce plancher.
Le réflexe n°1 : composer why-not. Tu lui demandes « pourquoi je ne peux pas installer en PHP 8.3 ? » et tu récupères le coupable, souvent noir sur blanc.
# Depuis ton projet
php -v
composer -V
# Montre quel package bloque PHP 8.3
composer why-not php 8.3.*Tu vas typiquement voir remonter un composant Symfony en 8.x (par exemple symfony/console ou symfony/error-handler) avec une contrainte du style « php >= 8.4 ». Et là, tout s’éclaire : ce n’est pas « Composer qui impose 8.4 au hasard », c’est une résolution qui t’emmène sur un package qui l’exige.
Ensuite, regarde ce que ton projet a effectivement résolu. Si tu as un lockfile, il est la vérité de ce qui est installé.
# Voir les versions installées (et l'arbre) côté Symfony
composer show symfony/console --tree
composer show symfony/error-handler --treeSi tu vois du 8.x, tu sais pourquoi PHP 8.4 est devenu obligatoire. Si tu es en 7.x mais que Composer te propose 8.x lors d’un update, c’est que tes contraintes autorisent la montée, et que quelque chose a déclenché un recalcul.
Enfin, un point que beaucoup oublient : la plateforme Composer. Si tu as un config.platform.php qui simule 8.3, Composer doit respecter 8.3 même si ton binaire local est en 8.4. Et si tu n’en as pas, Composer se base sur le PHP de la machine qui exécute. En CI, en container, sur ton Mac… ça peut expliquer des différences de résolution.
Trois stratégies réalistes (et les compromis qui vont avec)
Il n’y a pas de solution magique, juste des choix. Le mauvais move, c’est de bricoler dans vendor, ou de « forcer » en ignorant les contraintes. Tu vas gagner dix minutes et perdre une journée au prochain update.
1) Pinner Symfony en 7.x pour rester en PHP 8.3 (le contournement “pragmatique”)
Si ton projet est stable en PHP 8.3 et que tu veux juste arrêter l’hémorragie, tu peux forcer Symfony à rester en 7.x. C’est souvent le moyen le plus direct de rendre Composer à nouveau “content”.
Le principe n’est pas de verrouiller tout Symfony à la main, mais d’ajouter une contrainte explicite sur les composants qui te posent problème, pour empêcher Composer de partir sur 8.x. Ça peut ressembler à ça :
{
"require": {
"laravel/framework": "^13.3",
"symfony/console": "^7.2",
"symfony/error-handler": "^7.2"
},
"config": {
"platform": {
"php": "8.3.0"
}
}
}Je te le dis comme je le pense : c’est un contournement, pas une destination. Ça a deux risques. D’abord, tu peux te retrouver à bloquer des upgrades futures de Laravel ou d’autres packages qui, eux, voudront Symfony 8. Ensuite, tu vas devoir surveiller les compatibilités à un moment ou un autre. Mais pour débloquer une équipe qui doit livrer sans migrer de runtime tout de suite, c’est parfois la décision la plus saine.
2) Pinner Laravel (ou éviter l’update qui déclenche la résolution) quand tu veux “geler” le projet
Autre option : tu décides que tu ne veux pas bouger tant que ta prod n’est pas prête pour PHP 8.4. Dans ce cas, tu peux rester sur une version de Laravel qui ne laisse pas passer Symfony 8 dans ta résolution (selon le contexte exact), et surtout tu évites les updates larges qui font re-solver tout l’écosystème.
Concrètement, ça veut dire : tu commits le lockfile, tu fais des composer install partout (et pas des update “au feeling”), et quand tu ajoutes une dépendance, tu fais attention à ne pas entraîner une mise à jour globale des transitive deps sans l’avoir décidé. Ce n’est pas sexy, mais ça évite les « surprise upgrades » qui cassent la plateforme.
Évidemment, geler un projet a un coût. Tu repousses juste l’échéance. Si ton produit vit, tu finiras par devoir bouger.
3) Planifier l’upgrade PHP 8.4 (la solution “propre”, mais pas forcément immédiate)
La solution la plus simple sur le papier, c’est aussi la plus saine à moyen terme : passer le runtime en PHP 8.4 et arrêter de se battre contre la rivière. Si Symfony 8 arrive naturellement dans les résolutions, c’est généralement un signal : l’écosystème avance.
Ce que je recommande en vrai : fais d’abord passer la CI en double-run (8.3 et 8.4) si tu peux, ou au minimum un job PHP 8.4 “allowed to fail” pendant une semaine, juste pour voir. Beaucoup d’équipes se font piéger non pas par le code, mais par les extensions et l’image Docker : intl, gd, imagick, redis, pdo_pgsql, ou une conf OPCache un peu stricte qui se comporte différemment. Ça, ce n’est pas une migration PHP, c’est une migration d’environnement.
Ensuite tu upgrades un environnement de staging identique à prod, tu laisses tourner, tu observes tes erreurs réelles. Et seulement après, tu touches à la prod. Si tu fais l’inverse, tu vas associer « PHP 8.4 » à une journée de stress inutile, alors que c’est souvent une montée plutôt tranquille quand elle est préparée.
La règle qui évite ce genre de surprise en équipe : contrôler la résolution, pas juste les versions
Le pattern derrière ce bug n’est pas spécifique à Laravel. C’est la vie normale des dépendances transitives, juste avec un déclencheur violent (le plancher PHP). Si tu veux éviter de revivre ça tous les trois mois, adopte une règle simple : tu contrôles la résolution Composer comme un artefact.
Ça passe par un lockfile toujours commité et respecté, des updates regroupés et assumés (pas des updates “au fil de l’eau” quand quelqu’un a un moment), et un config.platform.php qui reflète la réalité de prod si tu as plusieurs environnements. Le jour où tu décides « on passe prod en 8.4 », tu changes la plateforme, tu fais un update volontaire, tu vois ce qui bouge, et tu testes. Pas l’inverse.
Et si tu utilises Renovate/Dependabot, pense à leur faire respecter ça : des PR de bump isolées, c’est bien, mais si elles déclenchent un re-solve massif côté transitive deps, tu veux le voir, le reviewer, et le merger au bon moment. Sinon, tu donnes à ton bot le pouvoir de changer ton runtime requirement sans même te demander ton avis.
Conclusion : ce n’est pas “Laravel vs PHP”, c’est “Composer fait son job”
Quand Composer te réclame PHP 8.4 sur un projet Laravel encore en 8.3, ça ressemble à un mensonge de framework. En pratique, c’est juste une résolution qui part sur Symfony 8, et Symfony 8 a avancé. La question n’est pas de savoir si Composer a raison. Il a raison. La question, c’est ce que toi tu veux optimiser maintenant : livrer vite en restant en 8.3 (en pinning proprement), geler un temps le projet, ou investir une petite semaine pour upgrader PHP et arrêter de te battre contre la gravité.
Si tu prends une seule habitude après ça : quand un message “PHP >= X” sort de nulle part, tu fais un composer why-not avant de toucher quoi que ce soit. Ça évite 80% des diagnostics au pif.