Pendant environ quatre mois, un de nos tableaux de bord d'admin a affiché « page 1 sur 0 » à chaque recherche sans résultat. Personne n'a ouvert de ticket. La ligne fautive était max(1, min($lastPage, $requestedPage)), qui te renvoie 1 quand $lastPage vaut 0 puis passe tranquillement à autre chose. Techniquement, rien n'était cassé. C'était juste un peu faux, et faux en silence, donc on ne l'a jamais remarqué. PHP 8.6.0 RC3 a été tagguée le 8 octobre et la disponibilité générale est prévue pour le 19 novembre. Après avoir épluché les changements, ma position est simple : ce que cette version apporte de plus précieux, c'est un tempérament, pas une nouveauté syntaxique. Le langage hausse le ton face aux entrées absurdes, et chaque nouvelle exception que tu croises pendant la migration est un rapport de bug que tu n'as pas eu à rédiger toi-même.

Prends la nouvelle fonction clamp(). Elle ferait un joli one-liner pour mon tableau de bord, mais elle refuse de jouer le jeu quand $min est supérieur à $max, ou quand l'une des bornes vaut NAN : elle lève une ValueError. Dans mon cas, c'est précisément la situation du résultat vide, donc le bug avec lequel j'ai vécu pendant des mois serait apparu dès la première recherche de charabia. La même humeur traverse toute la bibliothèque standard. Bon nombre de fonctions natives qui haussaient les épaules devant un flag absurde ou un nombre hors limites lèvent désormais une ValueError. preg_grep() renvoie false quand le moteur d'expressions régulières échoue en cours d'exécution, là où avant elle faisait comme si de rien n'était. base_convert() et ses cousines émettent une notice quand elles ne peuvent pas te donner une réponse exacte. Et un return dans un bloc finally est désormais déprécié, parce qu'il a toujours jeté discrètement à la poubelle l'exception qui était en train de remonter.

Même la nouvelle syntaxe qui brille suit la règle. L'application partielle de fonctions a été acceptée à l'unanimité, et une RFC de suivi l'a resserrée avant la sortie : chaque placeholder ? crée un paramètre obligatoire, même quand le paramètre sous-jacent a une valeur par défaut. Écris round(?, ?) et tu obtiens une closure qui exige à la fois le nombre et la précision, alors que round() se passe très bien du second. Si tu veux garder les valeurs par défaut, utilise ..., qui laisse les paramètres optionnels optionnels. J'aime beaucoup ce choix. Un point d'interrogation dans un appel doit vouloir dire « je vais te donner une valeur ici », et pas parfois « ou peut-être pas ».

Passons maintenant au meilleur argument contre tout ça, et je le prends au sérieux. Du point de vue de tes utilisateurs, une page qui affiche un nombre un peu bizarre vaut mieux qu'une page qui affiche une erreur. Une bonne partie du PHP en production a été ajustée pendant des années, surtout par accident, autour de l'ancien comportement tolérant. Les équipes avec une couverture de tests légère ne découvriront pas ces nouvelles exceptions en CI. Elles les découvriront par leurs clients, pendant un déploiement, un vendredi. Transformer des erreurs cachées en échecs visibles, c'est bien en principe. En pratique, ça fait retomber la facture sur la personne qui se trouve d'astreinte ce jour-là.

Je penche quand même du côté du bruit, en partie parce que le core s'y est pris avec beaucoup de soin. Les erreurs de flux peuvent maintenant être levées sous forme de StreamException, ce qui se configure via une option error_mode dans le contexte de flux, mais par défaut on reste sur des warnings. Le file_get_contents() de personne ne se met soudain à lever une exception sans qu'on le lui demande. Les dépréciations n'émettent que des E_DEPRECATED, donc ton code continue de tourner. Et quand une dépréciation semblait coûter plus de remue-ménage qu'elle n'en valait la peine, le vote a dit non : list() et l'alias gettext _() restent tous les deux. Ce n'est pas une équipe qui casse tout par purisme. Et l'écart de coût est bien réel. Une exception qui explose en staging te coûte un café et un petit patch. Une valeur fausse et silencieuse peut te coûter un an de lignes erronées qu'il faudra ensuite réconcilier à la main. RC4 et RC5 sont encore au calendrier, donc tu as le temps de brancher un job de CI sur la release candidate et de la laisser râler.

Voici le retournement qui me gêne. La seule nouveauté qui échoue en silence, c'est l'application partielle de fonctions, autrement dit justement celle qui enthousiasme tout le monde. Les arguments liés sont évalués une seule fois, au moment où la partielle est créée, et la closure en stocke les résultats. Imagine un worker de file d'attente qui tourne longtemps, construit une partielle de logging au démarrage et y passe date('c') comme argument. Chaque job qu'il traite six heures plus tard porte encore l'horodatage du démarrage. Pas d'erreur, pas de notice, pas de dépréciation. Une arrow function aurait appelé date() à chaque invocation. Les développeurs Go ont vécu des années avec un piège de capture similaire sur leurs variables de boucle avant que le langage ne change, donc on est en bonne compagnie. Mais dans une version par ailleurs si décidée à parler, c'est l'endroit où rien ne te dit rien, et je mettrais une règle de revue ou un contrôle d'analyse statique sur toute partielle dont les arguments liés contiennent un appel de fonction.

Alors voici ma question. Où faut-il tracer la ligne ? Une future version, ou ton outillage, devrait-elle avertir quand une partielle lie autre chose qu'une constante ou une simple variable ? Ou est-ce le genre d'assistanat que tu désactiverais dès le deuxième jour ? Et si tu as du code où un repli silencieux tient réellement la baraque, où une ValueError levée ferait plus de mal que la mauvaise réponse qu'elle remplace, raconte-le-moi en commentaire, parce que c'est le meilleur argument contre ma thèse et j'aimerais le voir.