Laravel 13, c’est le genre de sortie qui donne envie de cliquer sur “composer update” et de se dire que ça va passer. Et parfois… ça passe. Mais quand ça casse, ce n’est presque jamais “à cause d’une feature”. C’est parce que ton projet traîne des contraintes de versions, des packages oubliés, des bouts de code jamais retestés, et une CI qui couvre moins que ce que tu crois.
L’objectif ici n’est pas de te vendre Laravel 13. C’est de te donner un chemin d’upgrade propre, reproductible, et surtout moins anxiogène. On commence par les prérequis (PHP 8.3), on fait un audit rapide mais utile, on parle des options “opt-in” via attributs PHP (le truc qui peut soit clarifier ton code, soit rajouter de la magie), puis on finit avec une séquence de migration et des scénarios de tests à rejouer comme un adulte.
Le vrai point de départ : PHP 8.3, pas “Laravel 13”
La première claque, c’est rarement Laravel. C’est PHP. Laravel 13 monte le minimum à PHP 8.3, donc si ta prod tourne encore en 8.1 ou 8.2, tu n’es même pas en train de parler du même sujet. Le plan est simple : tu upgrades d’abord l’environnement, ensuite seulement le framework.
Et quand je dis “l’environnement”, je parle de tout ce qui peut te faire perdre une demi-journée bêtement : version PHP en prod et en CI, image Docker, config PHP-FPM si tu es en VM, extensions qui sautent, et même la version de Composer dans certains pipelines un peu vieux. Si tu veux une upgrade sans théâtre, mets au même niveau : local, CI, staging, prod. Sinon tu vas valider un build vert sur un PHP différent de celui qui sert réellement ton trafic.
Petit conseil terrain : si tu as plusieurs apps Laravel, ne commence pas par la plus critique. Prends une app secondaire mais “réaliste” (jobs, mails, auth, storage). Tu vas découvrir tes vrais points de friction sans le stress de la prod qui brûle.
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€ !
Audit express avant le “composer update” (celui qui te sauve)
Un upgrade Laravel, c’est souvent un upgrade de ton écosystème. Le framework est rarement seul dans la pièce. Tu as des packages qui pin des versions, des dépendances transverses (symfony/*, psr/*), et parfois une lib “mineure” qui bloque tout parce qu’elle n’a jamais été mise à jour.
Mon réflexe : regarder tout de suite ce qui va te coincer côté Composer. Pas besoin d’un roman, juste d’un diagnostic clair. Tu veux savoir si tu es bloqué par un package, par une contrainte PHP, ou par une contrainte de framework.
# 1) Vérifie ce que ton projet réclame réellement
php -v
composer -V
# 2) Mets à plat les dépendances et leurs contraintes
composer why-not laravel/framework 13.*
composer outdated -DSi why-not te sort un package obscur qui bloque tout, tu as ton premier vrai chantier. Soit tu le upgrades, soit tu le remplaces, soit tu l’isoles. Le pire move, c’est de forcer au pied de biche avec des --with-all-dependencies au hasard, sans comprendre ce qui est en train de bouger.
Ensuite, regarde ton codebase avec une question simple : “qu’est-ce qui est implicite ici ?”. Les vieux projets Laravel adorent l’implicite. Des macros, des observers, des providers qui enregistrent des bindings partout, des middlewares qui font plus que leur job. Ce n’est pas “mal”. Mais c’est exactement ce qui rend une régression difficile à diagnostiquer après upgrade.
Les attributs PHP “opt-in” : bonne idée… à condition de ne pas recréer une nouvelle magie
Laravel 13 pousse davantage de choses en “opt-in” via des attributs PHP. Sur le principe, j’aime bien. Un attribut bien choisi, c’est une intention posée sur le code, visible au même endroit que la méthode ou la classe concernée. C’est souvent plus lisible qu’un commentaire, et moins diffus qu’une config cachée à l’autre bout du projet.
Le piège, c’est d’en faire une nouvelle couche de magie. Si tu commences à empiler des attributs qui changent le comportement “à distance”, tu retombes dans le défaut classique : tu lis une méthode qui a l’air simple, mais elle se comporte différemment parce qu’un attribut déclenche une règle implicite. Et là, bon courage pour onboard un dev ou débugger un comportement en prod.
Ma règle perso : un attribut est une bonne idée quand il réduit l’écart entre ce que je lis et ce qui se passe. Il est une mauvaise idée quand il augmente cet écart. Dit autrement, je veux que l’attribut documente une intention, pas qu’il cache une surprise.
Concrètement, quand tu envisages d’activer une nouvelle option via attributs, pose-toi la question suivante : “si je retire cet attribut, est-ce que l’échec est évident ?”. Si la réponse est non, tu crées probablement un piège de maintenance.
Ma séquence d’upgrade Laravel 13 (celle qui évite les allers-retours)
Je vois encore des upgrades faits en mode “je change la version, je corrige ce qui casse, puis je prie”. Ça marche sur un projet toy. Sur un produit qui tourne, ça finit en patchwork.
La séquence qui tient bien, c’est : d’abord rendre l’exécution stable sur PHP 8.3, ensuite monter Laravel, ensuite seulement activer les nouveautés opt-in. Le point important, c’est que tu veux isoler les variables. Si tu upgrades PHP, Laravel, et que tu actives des options au même moment, tu n’as plus aucune idée de la cause quand un truc part de travers.
Donc oui, c’est volontairement “moins fun” : tu fais une branche dédiée, tu cales PHP 8.3 partout, tu fais passer le projet, tu merges. Ensuite seulement tu passes Laravel 13, tu stabilises. Et seulement après, tu joues avec les attributs et autres changements opt-in, avec des PRs petites et compréhensibles.
Upgrade côté code : là où ça casse vraiment (et comment ne pas perdre ta journée)
La majorité des vraies régressions viennent de trois zones : l’auth, les queues, et tout ce qui touche aux IO (mail, storage, HTTP). Parce que c’est là que tu as des bords, de la config, des services externes, et des comportements “qui marchaient” sans être vraiment testés.
Sur l’auth, tu veux vérifier les flows qui arrivent en prod, pas “la page login”. Typiquement, un refresh de session, un remember-me, un reset password, un middleware custom, une policy un peu tordue. Si tu as du multi-guard, rejoue les deux. Si tu utilises Sanctum/Passport, rejoue un token réel via une requête API qui traverse les middlewares de prod.
Sur les jobs, le piège classique c’est le “ça passe en sync en local” mais ça explose en worker. Rejoue une exécution queue réelle, avec le driver de prod (Redis, SQS, etc.), et vérifie surtout les retries, les timeouts, et les jobs qui sérialisent des modèles. Si tu as des jobs anciens qui sérialisent des closures ou des objets chelous, c’est le moment où ça va te revenir au visage.
Sur les mails, teste l’envoi mais aussi le rendu. Les templates Blade d’emails et les petites différences de config (from, reply-to, locale) sont des sources de régressions silencieuses. Le mail qui part mais qui arrive vide, c’est un grand classique. Et tu ne le verras pas sur un test “je vois 202 Accepted”.
Sur le storage, vérifie tes disques réellement utilisés. Le projet “simple” en local utilise local, la prod utilise s3, et tu as un disque public qui traîne pour des exports. Si tu n’as jamais rejoué un upload + un téléchargement + une suppression en conditions proches prod, tu n’as pas vraiment testé.
Les tests à rejouer après upgrade : pas “toute la suite”, les scénarios qui comptent
Si tu as une suite de tests solide, tant mieux, fais-la tourner et profite. Mais dans la vraie vie, beaucoup d’apps Laravel ont une couverture inégale. Et même avec de bons tests, il y a des scénarios de prod qui passent entre les mailles, surtout quand il y a du réseau ou des workers.
Ce que je rejoue systématiquement après un upgrade majeur, c’est une poignée de parcours critiques, mais rejoués comme en prod. Auth (login + refresh + logout), un endpoint API critique avec ses middlewares, un job qui fait un vrai aller-retour DB + storage, et un envoi d’email transactionnel. Pas besoin de 80 parcours. Juste ceux qui, s’ils cassent, te font une alerte client dans l’heure.
Et si tu veux un truc simple qui attrape des erreurs stupides, ajoute un smoke test qui boot l’app, tape deux routes, déclenche un job, et force un cache clear/rebuild. Ça paraît basique. C’est justement pour ça que ça marche.
# Exemple de smoke run “bête et méchant” en CI/staging
php artisan -V
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan migrate --force
php artisan queue:work --onceDernière mise en garde : ne confonds pas “ça marche en debug” et “ça marche avec caches et optimisations”. Beaucoup de surprises arrivent quand tu remets config:cache et route:cache. Si ta prod les utilise, teste avec. Sinon tu valides un comportement qui n’existera pas en prod.
Mon avis (tranché) sur l’upgrade : le risque, c’est la dette cachée, pas Laravel
Laravel 13 va apporter des trucs sympas, et les attributs opt-in peuvent rendre certaines intentions plus nettes. Mais si tu prends une claque, ce sera presque toujours parce que ton projet a une dette de “réalité” : des bouts jamais exécutés en CI, des packages figés, des environnements qui divergent, des jobs qui n’ont jamais tourné en conditions proches prod.
La bonne nouvelle, c’est que l’upgrade est une occasion parfaite pour remettre de l’ordre. Pas en écrivant une doc de 40 pages. Juste en alignant les versions, en identifiant ce qui bloque, et en rejouant les scénarios qui font réellement tourner ton produit. Le reste, c’est du bruit.