Hace un tiempo perdí una tarde por un ajuste de reintento con valor 300. El desarrollador que lo escribió pensaba en milisegundos. El worker que lo leía asumía segundos, así que cada trabajo fallido se quedaba aparcado cinco minutos y la cola se atascó hasta que alguien me llamó de madrugada. Nada en el sistema de tipos podría haberlo detectado, porque ambos lados solo se pasaban un int de un lado a otro. PHP 8.6, previsto para el 19 de noviembre de 2026, por fin ataca de frente esa clase de bug, y eso, más que cualquier sintaxis nueva, es lo que me tiene esperando esta versión.

backoff.php
<?php
use Time\Duration;

// backoff math stays in duration space, no unit juggling
$base = Duration::fromMilliseconds(100);
$attempt = 5;
$delay = $base->multiplyBy(2 ** $attempt);

$timeout = Duration::fromSeconds(2)
    ->add(Duration::fromMilliseconds(500));

if ($delay < $timeout) {
    usleep($delay->seconds * 1000000);
}

La funcionalidad que todo el mundo va a enseñar en los meetups es la aplicación parcial de funciones. Escribes number_format(?, 2, '.', ' ') y recibes de vuelta un closure que espera el argumento que falta, con ... disponible para dejar abiertas colas enteras de una firma. Combinada con el operador pipe significa que una cadena de transformaciones ya no necesita una arrow function desechable en cada paso. Suma propiedades readonly con valores por defecto, #[\Override] extendido a constantes de clase y enums que pueden implementar __debugInfo(), y tienes un buen surtido. Todo muy bien. Pero mi voto para el titular va a Time\Duration.

Duration es un tipo de valor en condiciones para lapsos de tiempo. Construyes uno con fromSeconds() o fromMilliseconds(), e internamente guarda tres partes: segundos, nanosegundos y un flag de negativo, de modo que 1500 milisegundos se convierten en un segundo más 500 millones de nanosegundos sin que tú hagas la aritmética. Las instancias son inmutables, add() y multiplyBy() devuelven objetos nuevos, y dos duraciones se comparan directamente con < y compañía. El backoff exponencial se vuelve una multiplicación sobre un valor que sabe lo que es. Hace años que dejamos de guardar dinero como floats pelados porque la unidad se perdía por el camino; un timeout guardado como entero desnudo es la misma trampa, y 8.6 por fin la cierra.

Alrededor de esa pieza central hay estandarizadores más pequeños que yo llamaría legislación de calidad de vida. clamp($volume, 0, 100) sustituye la contorsión de max() envuelto en min() que cada codebase reinventaba, y acepta cualquier cosa comparable, DateTimeImmutable incluido. SortDirection llega como enum del núcleo con los casos Ascending y Descending, así que el eterno 'asc' contra 'ASC' contra true en los métodos de repositorio consigue un tipo de verdad. Y los mensajes de JsonException ahora llevan coordenadas, reportando un error de sintaxis cerca de la posición 1:3 en lugar de encogerse de hombros desde algún punto de un payload de 40 KB. Cada una de estas cosas es diminuta. Cada una también jubila una función helper que existía en cinco sabores incompatibles por todo tu directorio vendor.

Y luego está la pieza que la gente de servidor debería estudiar: la nueva Polling API. Io\Poll\Context permite que un proceso registre handles de stream con eventos como Event::Read y luego haga wait() sobre todos a la vez, con PHP eligiendo por debajo el mejor mecanismo del sistema operativo: epoll en Linux, kqueue en macOS y BSD, WSAPoll en Windows, event ports o poll a secas en el resto. Hasta ahora la respuesta incorporada era stream_select(), que hereda los techos de escalado de la vetusta syscall select(). Para ser claro con el alcance: esto es una primitiva de espera, sin scheduler, sin event loop, sin palabra clave async. Pero proyectos como Amp y ReactPHP, que hoy o se apoyan en stream_select() o le piden a tu equipo de ops que instale una extensión extra, consiguen una base nativa en cada build de PHP por defecto. Y sí, wait() acepta un Duration como timeout: cero para una comprobación no bloqueante, null para bloquear hasta que pase algo. Las piezas nuevas ya encajan entre sí.

Ahora el contrapunto honesto. La sintaxis es lo que cambia tus diffs desde el primer día, y la aplicación parcial va a remodelar el código cotidiano de forma mucho más visible que cualquier objeto Duration. Lo concedo, y añadiría las salvedades que conviene conocer: los argumentos que suministras al construir un parcial se evalúan ahí mismo, en el momento de la creación, así que una lectura de configuración horneada en un parcial queda congelada mientras la misma lectura dentro de una arrow function se reejecuta en cada llamada. Y un parcial es un closure anónimo nuevecito; pregúntale a reflection quién es y no te dirá nada de la función que envuelve, mientras que strlen(...) conserva su identidad, atributos incluidos. ¿Por qué sigo poniendo más arriba el trabajo de biblioteca? Porque siempre pudiste simular currying con fn() y un poco de disciplina. Ningún paquete de composer puede hacer de Duration el tipo de unidad en el que coincidan los drivers de PDO, los clientes HTTP y tu propio código, y ninguno puede meter epoll en el runtime por defecto. Solo el núcleo puede acuñar vocabulario compartido.

Antes de actualizar, presupuesta la limpieza. Los valores por defecto de sesión salen más estrictos de fábrica: session.use_strict_mode, cookie_httponly y un SameSite con valor Lax son ahora la base, lo cual es la decisión correcta pero va a sorprender a los flujos de login entre sitios y a cualquier JavaScript que lea la cookie de sesión. Hacer return desde un bloque finally queda deprecado, igual que devolver valores desde __construct() y __destruct(). spl_object_hash() está de salida; lo sustituye spl_object_id(), pero devuelve un int en vez de una cadena de 32 caracteres, así que haz grep del código que guarda o compara esos hashes. Alias legacy como is_double(), además de strcoll(), metaphone() y la coerción de objeto a array, se suman a la lista de deprecaciones, los identificadores let, is y readonly pasan a estar reservados para nombres de clase y de función, y la familia trim() ahora elimina caracteres form-feed por defecto. PHPCompatibility, Rector y un baseline de PHPStan convierten todo esto en una checklist en lugar de una crisis.

Aquí es donde de verdad quiero que me lleves la contraria. Cada una de estas adiciones se solapa con algo que el userland ya construyó: objetos de tiempo al estilo Carbon, enums de ordenación de framework, helpers clamp hechos a mano, ext-uv. Un bando dice que el núcleo, al absorber este territorio, por fin nos da una convención en lugar de seis. El otro dice que simplemente añade una séptima, y que el dolor de interoperabilidad entre Time\Duration y DateInterval, o entre SortDirection y lo que tu ORM inventó en 2019, durará más que el beneficio. ¿Tú dónde te posicionas, y qué debería estandarizar el núcleo a continuación: un tipo de dinero, un objeto URI de verdad, o nada de nada durante una temporada?