PHP 8.6, attendu en novembre 2026, embarquera une fonction clamp(), et j'entends déjà la section commentaires chauffer : « C'est juste min(max($n, $min), $max). Pourquoi ça mérite une RFC ? » Voici ma position, énoncée sans détour : clamp() mérite sa place dans l'espace de noms global, et la raison n'a presque rien à voir avec l'économie de frappe. La raison, c'est que la version native échoue bruyamment là où toutes les versions maison échouent en silence.

Regarde ce que la RFC spécifie vraiment. Si tu appelles clamp() avec un min supérieur au max, tu obtiens une ValueError. Passe NAN comme borne, pareil : ValueError. Maintenant va faire un grep dans ta base de code pour retrouver le helper clamp que quelqu'un a écrit en 2019. Je te parie qu'il tient en une ligne autour de min() et max(), et je parie encore plus gros qu'il ne fait aucune des deux vérifications. Donne à min(max(50, 100), 10) un intervalle inversé et il te tend joyeusement 10 — une mauvaise réponse livrée avec une confiance totale. C'est le genre de bug qui survit à la revue de code, passe les tests du chemin heureux, et refait surface six mois plus tard sous la forme d'une remise qui se cale sur le mauvais plafond sur une page de paiement. Une ValueError lancée au point d'appel l'aurait attrapé dès la première exécution des tests.

Number::clamp de Laravel raconte la même histoire depuis l'autre côté. C'est la composition min(max()) à l'identique, enveloppée dans une méthode avec des types de paramètres int|float — raisonnable, mais sans validation de l'intervalle non plus. Inverse les arguments min et max et elle calcule un truc à l'air plausible au lieu de protester. Ce n'est pas une pique contre le framework ; c'est une preuve à l'appui de ma thèse. Quand tout le monde réimplémente les trois mêmes lignes, tout le monde réimplémente aussi la même gestion d'erreurs manquante. Déplacer la fonction dans le cœur du langage, c'est faire traiter les cas limites une seule fois, par des gens qui ont passé un cycle de RFC à y réfléchir, au lieu de mille fois par des gens qui ne l'ont pas fait.

Laisse-moi concéder honnêtement le contre-argument le plus solide, parce qu'il est réel : la bibliothèque standard n'est pas un tiroir fourre-tout, et « je vois bien quelqu'un utiliser ça » est le chemin qui mène à un langage avec quatre façons de tout faire et une documentation que personne ne peut garder en tête. Chaque fonction globale, c'est un nom réservé pour toujours, une surface de rétrocompatibilité, une obligation de polyfill. Si clamp() n'était que du sucre, je serais du côté des sceptiques. Mais ce n'est pas que du sucre — c'est une amélioration de correction déguisée en sucre, plus un vocabulaire partagé. clamp($percent, 0, 100) se lit comme une intention ; min(max($percent, 0), 100) se lit comme un puzzle où tu revérifies quelle borne va où. J'ai relu assez de PR où quelqu'un l'avait inversé pour savoir que ce puzzle a un taux d'échec non nul.

Passons maintenant à la partie qui me rend nerveux. Les paramètres sont effectivement mixed : tout ce qui supporte les comparaisons < et > est admissible. Ça veut dire que les chaînes non numériques se font borner lexicographiquement — clamp('D', 'A', 'C') renvoie 'C' — et que des objets comme DateTimeImmutable fonctionnent aussi, donc tu peux caler une date dans un intervalle de dates. Le cas d'usage des dates est franchement charmant ; j'ai écrit exactement cette échelle de if/else pour des fenêtres de réservation plus de fois que je n'aimerais l'admettre, et clamp(value: $date, min: $windowStart, max: $windowEnd) est un vrai progrès. Mais les chaînes ? Dans un langage où la sémantique des comparaisons a historiquement été un rite de bizutage, mettre entre les mains des gens une fonction qui va joyeusement « borner » des chaînes arbitraires, ça ressemble à laisser le cran de sûreté ouvert. Personne ne prévoit de comparer 'AAA' à 'AAAA' ; ça arrive quand un paramètre de requête débarque sous forme de chaîne et que personne ne l'a casté.

Donc mon conseil pratique, si tu adoptes ça en 8.6 : traite clamp() comme un outil numérique-et-datetime par convention, même si la signature autorise plus. Caste tes entrées à la frontière comme tu devrais déjà le faire de toute façon, et laisse l'analyse statique signaler les cas bizarres. Et si tu es sur une version antérieure à 8.6 — c'est-à-dire nous tous pour un moment encore — soit continue avec min(max()) accompagné d'une assertion explicite que min <= max, soit écris dès maintenant la version userland de cinq lignes avec le même comportement de ValueError, pour que la migration à venir soit un chercher-remplacer plutôt qu'un changement de comportement.

Le motif plus large ici, c'est celui dans lequel j'aimerais voir PHP s'installer : de petites fonctions ennuyeuses dont toute la justification est de bien traiter les cas limites. Chaque amélioration du langage n'a pas besoin d'être des génériques ou un nouveau modèle d'exécution. Parfois, le progrès, c'est la bibliothèque standard qui absorbe discrètement le fichier de helpers que chaque équipe copie-colle de projet en projet depuis toujours — et qui l'absorbe avec de meilleures garanties qu'aucune de nos copies n'en a jamais eu.

Ce qui me laisse avec une vraie question pour toi, parce que j'hésite moi-même : où mets-tu la limite pour l'inclusion dans la stdlib ? Prendrais-tu aussi array_first(), str_between(), un retry() natif ? Ou est-ce que clamp() est précisément là où tu t'arrêterais — une primitive bourrée de cas limites qui entre, et tout le reste qui reste en userland ? Dis-moi où tu traces ta ligne, et surtout, pourquoi là.