Sur beaucoup de projets PHP, le “contrat” implicite c’est : une fonction interne foire, elle renvoie false, et on avise. Sauf qu’en pratique, on n’avise pas toujours. On loggue parfois. Ou on laisse couler. PHP 8.6 change la donne sur une série de cas où des retours false pour arguments invalides seraient remplacés par des ValueError. C’est plus propre. Et c’est aussi un très bon moyen de découvrir que ton code s’appuyait sur du silence.
Le piège, ce n’est pas « ça casse au démarrage ». C’est « ça casse le jour où un path contient un caractère bizarre, un mode est invalide, un droit est absent, ou un paramètre est hors plage ». Autrement dit : en prod, sur un cas rare, à 2h du matin.
Ce qui change : le “contrat” d’erreur devient une exception
La logique derrière cette conversion est assez simple : quand l’appel est intrinsèquement invalide (argument hors domaine, valeur impossible), renvoyer false est un mauvais signal. Le false ressemble à un échec “normal” (droit manquant, fichier absent, réseau instable), alors qu’on est parfois face à un bug d’appelant. Une ValueError dit clairement « tu m’as appelé n’importe comment ».
Dans l’RFC discutée pour PHP 8.6, plusieurs fonctions internes sont citées dans ce mouvement, notamment côté filesystem et utilitaires, et aussi des fonctions liées à mysqli. Le détail exact peut évoluer, mais l’intention est nette : des erreurs d’arguments qui “retournaient faux” vont devenir des exceptions. Si tu ne catches pas, ton process s’arrête. Sur un worker queue, ça peut tuer un job. Sur un web, ça peut sortir une 500.
Le point important à garder en tête : ça ne remplace pas tous les false par des exceptions. Tu auras encore des false sur des échecs “du monde réel”. Là on parle surtout des appels invalides, donc des erreurs que tu préfères détecter tôt.
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 ça casse “en vrai” : le code qui faisait semblant de gérer l’erreur
Dans une base PHP mature, on voit souvent ce pattern : une fonction peut renvoyer false, donc on fait un if ($result === false)… mais seulement sur le chemin principal. Et ailleurs, on fait confiance à l’environnement. Parce que « ça marche depuis des années ».
Le changement en ValueError transforme un bug discret en crash franc. Et le crash ne se produira pas forcément là où tu testes. Il se produira là où tu as des entrées moins propres. Exemple tout bête : un nom de fichier qui vient d’un import, un répertoire calculé à partir d’un slug, un mode de création issu de config, une option mysqli passée par une couche d’abstraction.
Autre effet collatéral : les libs ou wrappers qui normalisaient tout en “bool + log” vont devenir des points de rupture. Le false
Les zones à risque (celles qu’on retrouve sur des apps web)
Sur une app Laravel/Symfony ou du PHP “maison” raisonnable, les cas qui reviennent sont rarement exotiques. Ils sont juste fréquents, et répartis dans tout le code.
Filesystem : mkdir, chmod, copy… et les paths que tu ne maîtrises pas à 100%
Le filesystem, c’est l’endroit rêvé pour les erreurs “rarement reproductibles”. Entre les droits, les chemins relatifs, les caractères inattendus et les environnements qui ne sont pas identiques, tu as forcément un call qui finira sur un cas limite.
Si demain certains appels invalides passent de false à ValueError, ton code qui faisait « si false alors log » ne suffira plus, parce que tu ne rentreras même pas dans le if. Tu tomberas avant.
Logs : error_log et les “messages” pas si string que ça
error_log() traîne dans énormément de projets, parfois via un fallback quand Monolog n’est pas prêt, parfois dans des scripts cron, parfois dans des bouts d’admin écrits vite. Si tu as déjà vu passer des arrays balancés en paramètre, des objets non stringifiés, ou des destinations de log malformées, tu vois le souci : un argument invalide qui “passait” peut se transformer en exception.
Permissions : chmod et la fausse simplicité des modes
Les modes, on pense les maîtriser. Et pourtant, entre les valeurs en octal, les conversions de config, les casts implicites, et les morceaux de code copiés-collés d’un vieux snippet, tu peux rapidement te retrouver avec une valeur hors domaine. Là encore, si PHP décide que c’est une erreur d’argument, tu prends une exception.
mysqli : options, flags, et couches d’abstraction qui “généralisent”
Côté mysqli, c’est souvent la couche d’accès DB qui masque le problème. Une option passée via une config, un flag invalide, une combinaison non supportée, et tu te retrouves avec du code qui attendait false pour déclencher un fallback. Si ça devient une ValueError, il faut que l’exception soit absorbée au bon niveau, ou qu’elle devienne volontairement fatale (mais alors assumée).
Ce que je conseille : décider où tu veux du “fatal” et où tu veux du “gérable”
Le réflexe facile, c’est de mettre des try/catch partout. Mauvaise idée. Tu vas noyer les vrais bugs, et tu vas rendre le comportement impossible à comprendre. Le bon compromis, c’est de clarifier deux catégories.
La première, c’est l’erreur qui doit être fatale car elle révèle un bug de code ou de configuration. Typiquement : un mode de chmod invalide fourni par une config. Là, une ValueError est ton amie. Tu veux que ça casse en CI, pas que ça se transforme en “false, on continue”.
La deuxième, c’est l’erreur “métier” ou environnementale où tu veux pouvoir revenir à l’utilisateur ou au job avec un état gérable. Exemple : un fichier temporaire impossible à créer car le disque est plein. Ce n’est pas un argument invalide, mais ça illustre où tu veux des retours contrôlés.
Avec PHP 8.6, la bascule pousse ton code à être plus honnête. À toi de choisir où tu mets la barrière.
Patterns de mitigation qui tiennent en prod
Il y a trois tactiques qui marchent bien, et qui évitent l’effet “on catch tout et on prie”.
1) Valider en amont, au plus près des entrées
Quand l’argument vient d’une entrée non fiable (import, request, config, DB), fais le boulot avant d’appeler la fonction interne. Un path, ça se normalise. Un mode, ça se borne. Une option, ça se whitelist. Ça ne rend pas ton code plus verbeux, ça le rend plus prévisible.
Ce n’est pas glamour, mais c’est ce qui évite le crash sur un cas rare.
2) Try/catch ciblé, au niveau “frontière”
Tu ne catches pas une ValueError au milieu de ta logique métier. Tu la catches à l’endroit où tu parles au monde extérieur, ou à un endroit où tu sais convertir l’échec en un état explicite (exception domaine, retour d’erreur structuré, log + fallback).
<?php
function safeCopy(string $from, string $to): bool
{
// Ici, on assume que les chemins sont déjà validés/normalisés.
try {
return copy($from, $to);
} catch (ValueError $e) {
// Argument invalide = bug ou donnée sale.
// À toi de décider : log + false, ou rethrow.
error_log('copy() ValueError: ' . $e->getMessage());
return false;
}
}
Ce wrapper a un défaut : il peut masquer un bug si tu choisis de retourner false. Dans certains contextes (upload, génération de fichier critique), je préfère rethrow en exception applicative, et faire échouer le process clairement. Mais l’idée est là : un seul endroit où tu contrôles la conversion.
3) Écrire des tests qui touchent les chemins d’erreur “moches”
Les ValueError qui vont te tomber dessus ne viendront pas de ton “happy path”. Elles viendront des cas que tu n’aimes pas tester : permissions, répertoire manquant, config invalide, entrées tordues. C’est précisément ces cas-là que tu dois figer avec des tests, pas parce que tu aimes la souffrance, mais parce que ça stabilise l’upgrade.
Et non, ça ne veut pas dire monter une usine à mocks. Ça veut dire avoir quelques tests d’intégration qui manipulent réellement des fichiers temporaires, et quelques tests unitaires sur tes fonctions de normalisation/validation.
Stratégie CI : attraper ça avant la prod, pas dans les logs
Le plan le plus rentable, c’est de faire tourner ton projet sur plusieurs versions de PHP en parallèle. Pas pour le sport. Pour voir apparaître les exceptions sur les scénarios existants. Sur un projet avec une suite de tests décente, c’est souvent là que tu découvres les “false” jamais checkés, ou checkés au mauvais endroit.
Concrètement, tu veux une matrice qui couvre ta version actuelle, ta cible, et idéalement une version “future” quand elle est dispo sur ton infra CI (nightly ou RC, selon ta tolérance). Et tu veux arrêter de sous-déclarer les erreurs. Si ton app avale tout avec un reporting minimal en test, tu te tires une balle dans le pied.
Sur les suites PHPUnit, je préfère un mode où les warnings/notices qui comptent sont visibles (voire convertis en exceptions dans certains projets). Pas pour rendre les builds rouges par principe, mais pour empêcher le retour du « ça marche quand même ». Le jour où une ValueError arrive, tu ne veux pas la découvrir sur une route obscure appelée une fois par semaine.
Un exemple typique de casse : le code qui faisait “if false”
Voilà le genre de code que je vois encore souvent dans des scripts et des jobs. Ça n’a rien de honteux. C’est juste fragile quand le contrat change.
<?php
// Avant : on s'attend à false en cas de problème.
if (false === mkdir($path, $mode, true)) {
error_log('Impossible de créer le dossier: ' . $path);
return;
}
// Avec des conversions en ValueError sur arguments invalides,
// ce code peut ne jamais atteindre le if.
La correction n’est pas “rajoute un try/catch partout”. La correction, c’est d’abord de te demander pourquoi $path ou $mode pourraient être invalides. Si c’est une donnée externe, tu la nettoies. Si c’est une config, tu la valides au boot. Et si malgré tout tu veux une dégradation contrôlée, tu catches précisément à la frontière où tu peux décider quoi faire.
Mon avis (sec) : PHP fait le ménage, à toi de faire le tien
Je suis plutôt pour ce genre de changement. Les retours false sur des appels invalides, c’est un héritage qui encourage le code “approx”. Et l’approx finit toujours par coûter plus cher que la rigueur, juste pas au même moment.
Par contre, ce n’est pas un changement à prendre comme une simple montée de version. Si tu as une base de code qui mélange filesystem, scripts et couches d’abstraction, tu dois traiter ça comme un mini-projet : cartographier où tu dépends de retours false, mettre des garde-fous aux bons endroits, et faire parler la CI. C’est exactement le genre de BC break qui ne se voit pas au build, mais qui te plante une exécution rare. Donc oui, prépare-toi maintenant, avant que PHP 8.6 devienne “la version par défaut” dans ton hébergeur ou ton image Docker.