PHP 8.6, due in November 2026, will ship a clamp() function, and I can already hear the comment section warming up: 'That's just min(max($n, $min), $max). Why does this need an RFC?' Here's my position, stated plainly: clamp() deserves its spot in the global namespace, and the reason has almost nothing to do with saving keystrokes. The reason is that the built-in version fails loudly where every homemade version fails silently.

Look at what the RFC actually specifies. If you call clamp() with a min greater than max, you get a ValueError. Pass NAN as a bound, same thing: ValueError. Now go grep your codebase for the clamp helper somebody wrote in 2019. I'll bet money it's a one-liner wrapping min() and max(), and I'll bet more money it does neither check. Feed min(max(50, 100), 10) an inverted range and it cheerfully hands you 10 — a wrong answer delivered with total confidence. That's the kind of bug that survives code review, passes the happy-path tests, and surfaces six months later as a discount that clamps to the wrong ceiling on a checkout page. A thrown ValueError at the call site would have caught it in the first test run.

Laravel's Number::clamp tells the same story from the other side. It's the identical min(max()) composition, wrapped in a method with int|float parameter types — sensible, but no range validation either. Swap the min and max arguments and it computes something plausible-looking instead of objecting. This isn't a knock on the framework; it's evidence for my thesis. When everyone reimplements the same three lines, everyone also reimplements the same missing error handling. Moving the function into core means the edge cases get handled once, by people who spent an RFC cycle thinking about them, instead of a thousand times by people who didn't.

Let me concede the strongest counter-argument honestly, because it's a real one: the standard library is not a junk drawer, and 'I could see someone using this' is how you end up with a language that has four ways to do everything and documentation nobody can hold in their head. Every global function is a name claimed forever, a BC surface, a polyfill obligation. If clamp() were only sugar, I'd side with the skeptics. But it isn't only sugar — it's a correctness upgrade disguised as sugar, plus a shared vocabulary. clamp($percent, 0, 100) reads as intent; min(max($percent, 0), 100) reads as a puzzle where you double-check which bound goes where. I've reviewed enough PRs where someone flipped it to know that puzzle has a nonzero failure rate.

Now for the part where I get nervous. The parameters are effectively mixed: anything that supports < and > comparisons is fair game. That means non-numeric strings get clamped lexicographically — clamp('D', 'A', 'C') returns 'C' — and objects like DateTimeImmutable work too, so you can pin a date into a range of dates. The date use case is genuinely lovely; I've written that exact if/else ladder for booking windows more times than I'd like to admit, and clamp(value: $date, min: $windowStart, max: $windowEnd) is a real improvement. But strings? In a language where comparison semantics have historically been a hazing ritual, handing people a function that will happily 'clamp' arbitrary strings feels like leaving the safety off. Nobody plans to compare 'AAA' against 'AAAA'; it happens when a request parameter arrives as a string and nobody cast it.

So my practical advice, if you adopt this in 8.6: treat clamp() as a numeric-and-datetime tool by convention, even though the signature allows more. Cast your inputs at the boundary like you should be doing anyway, and let static analysis flag the weird cases. And if you're on anything before 8.6 — which is all of us for a while yet — either keep using min(max()) with an explicit assertion that min <= max, or write the five-line userland version with the same ValueError behavior now, so the eventual migration is a find-and-replace rather than a behavior change.

The bigger pattern here is one I'd like to see PHP lean into: small, boring functions whose entire justification is doing the edge cases right. Not every language improvement needs to be generics or a new runtime model. Sometimes progress is the standard library quietly absorbing the helper file every team has been copy-pasting between projects since forever — and absorbing it with better guarantees than any of our copies had.

Which leaves me with a genuine question for you, because I go back and forth on it: where's your line for stdlib inclusion? Would you also take array_first(), str_between(), a built-in retry()? Or is clamp() precisely where you'd stop — one edge-case-laden primitive in, everything else stays in userland? Tell me where you'd draw it, and more importantly, why there.