PHP 8.6, previsto para noviembre de 2026, incluirá una función clamp(), y ya puedo oír la sección de comentarios calentando motores: '¿Eso no es min(max($n, $min), $max)? ¿Para qué hace falta un RFC?' Mi postura, dicha sin rodeos: clamp() se merece su sitio en el espacio de nombres global, y la razón no tiene casi nada que ver con ahorrar pulsaciones de teclado. La razón es que la versión nativa falla a gritos donde toda versión casera falla en silencio.
Mira lo que especifica realmente el RFC. Si llamas a clamp() con un min mayor que max, obtienes un ValueError. ¿Pasas NAN como límite? Lo mismo: ValueError. Ahora haz grep en tu base de código buscando el helper de clamp que alguien escribió en 2019. Apuesto dinero a que es un one-liner que envuelve min() y max(), y apuesto más dinero a que no hace ninguna de las dos comprobaciones. Dale a min(max(50, 100), 10) un rango invertido y te entrega alegremente 10 — una respuesta errónea servida con total seguridad. Ese es el tipo de bug que sobrevive a la revisión de código, pasa los tests del camino feliz y aflora seis meses después como un descuento que se recorta contra el techo equivocado en una página de pago. Un ValueError lanzado en el punto de llamada lo habría cazado en la primera ejecución de tests.
Number::clamp de Laravel cuenta la misma historia desde el otro lado. Es la composición idéntica de min(max()), envuelta en un método con tipos de parámetro int|float — sensato, pero tampoco valida el rango. Intercambia los argumentos de min y max y calcula algo con pinta plausible en lugar de protestar. Esto no es un reproche al framework; es evidencia a favor de mi tesis. Cuando todo el mundo reimplementa las mismas tres líneas, todo el mundo reimplementa también el mismo manejo de errores ausente. Mover la función al núcleo significa que los casos límite se resuelven una vez, por gente que dedicó un ciclo de RFC a pensarlos, en lugar de mil veces por gente que no lo hizo.
Déjame conceder honestamente el contraargumento más fuerte, porque es real: la biblioteca estándar no es un cajón de sastre, y 'me imagino a alguien usando esto' es como acabas con un lenguaje que tiene cuatro maneras de hacer cada cosa y una documentación que nadie puede retener en la cabeza. Cada función global es un nombre reclamado para siempre, una superficie de compatibilidad, una obligación de polyfill. Si clamp() fuera solo azúcar, me pondría del lado de los escépticos. Pero no es solo azúcar — es una mejora de corrección disfrazada de azúcar, más un vocabulario compartido. clamp($percent, 0, 100) se lee como intención; min(max($percent, 0), 100) se lee como un acertijo en el que compruebas dos veces qué límite va dónde. He revisado suficientes PRs donde alguien lo invirtió como para saber que ese acertijo tiene una tasa de fallo distinta de cero.
Ahora la parte que me pone nervioso. Los parámetros son en la práctica mixed: cualquier cosa que soporte comparaciones con < y > entra en juego. Eso significa que las cadenas no numéricas se recortan lexicográficamente — clamp('D', 'A', 'C') devuelve 'C' — y objetos como DateTimeImmutable también funcionan, así que puedes acotar una fecha dentro de un rango de fechas. El caso de las fechas es genuinamente precioso; he escrito exactamente esa escalera de if/else para ventanas de reserva más veces de las que me gustaría admitir, y clamp(value: $date, min: $windowStart, max: $windowEnd) es una mejora real. ¿Pero cadenas? En un lenguaje donde la semántica de comparación ha sido históricamente un rito de novatada, darle a la gente una función que 'recorta' encantada cadenas arbitrarias se siente como dejar el seguro quitado. Nadie planea comparar 'AAA' contra 'AAAA'; ocurre cuando un parámetro de la petición llega como string y nadie lo casteó.
Así que mi consejo práctico, si adoptas esto en 8.6: trata clamp() como una herramienta para números y fechas por convención, aunque la firma permita más. Castea tus entradas en la frontera, como deberías estar haciendo de todos modos, y deja que el análisis estático señale los casos raros. Y si estás en cualquier versión anterior a 8.6 — que somos todos durante un buen rato todavía — o sigues usando min(max()) con una aserción explícita de que min <= max, o escribes ya la versión userland de cinco líneas con el mismo comportamiento de ValueError, para que la migración final sea un buscar-y-reemplazar y no un cambio de comportamiento.
El patrón más grande aquí es uno en el que me gustaría ver a PHP apoyarse: funciones pequeñas y aburridas cuya justificación entera es hacer bien los casos límite. No toda mejora del lenguaje tiene que ser genéricos o un nuevo modelo de runtime. A veces el progreso es que la biblioteca estándar absorba discretamente el archivo de helpers que cada equipo lleva copiando y pegando entre proyectos desde siempre — y que lo absorba con mejores garantías que las que tenía cualquiera de nuestras copias.
Lo cual me deja con una pregunta genuina para ti, porque yo mismo voy y vengo con ella: ¿dónde está tu línea para la inclusión en la stdlib? ¿Aceptarías también array_first(), str_between(), un retry() nativo? ¿O clamp() es exactamente donde te plantarías — una primitiva cargada de casos límite dentro, y todo lo demás se queda en userland? Dime dónde trazarías la línea y, más importante aún, por qué ahí.
Comentarios
Aún no hay comentarios — escribe el primero.
Inicia la conversación
Sin cuenta ni contraseña — introduce tu correo y te enviamos un enlace de acceso de un solo uso. ¿Primera vez? Todo se configura automáticamente.
Tu valoración se aplicará automáticamente al iniciar sesión.
Revisa tu bandeja de entrada
Hemos enviado un enlace de acceso a …. Ábrelo en este dispositivo — esta pestaña te conectará automáticamente.
Esperando tu clic …
·