Saltar al contenido

Todo lo nuevo en PHP 8.6

Publicado el en Artículos
Portada del artículo Todo lo nuevo en PHP 8.6.

PHP 8.6 todavía no es estable. En el momento de escribir esto, php.net anuncia la RC4 y el archivo NEWS de la rama PHP-8.6 ya registra la 8.6.0RC5. El calendario oficial de la versión (wiki.php.net/todo/php86) sitúa la disponibilidad general a finales de noviembre de 2026. Hasta entonces, puede cambiar algún detalle: todo lo que sigue está contrastado con las RFC de wiki.php.net y con los archivos UPGRADING y NEWS de la rama PHP-8.6.

Lo miro desde dos sitios: el día a día en Laravel y los pipelines de limpieza de datos, donde cada array_map, cada fopen y cada json_decode se ejecutan millones de veces.

1. Aplicación parcial de funciones

Escribes ? en lugar de un argumento y obtienes una closure que espera ese argumento. ... deja abiertos todos los que falten.

$quitarTabs = str_replace("\t", ' ', ?);
$filas = array_map($quitarTabs, $filas);

$f = foo(1, ?, 3, ...); // closure: falta el 2.º argumento y los demás quedan abiertos

En limpieza de datos esto sustituye a muchas fn($x) => ... de una línea. Fuente: RFC partial_function_application_v2.

2. Time\Duration

Un tipo nativo para una cantidad de tiempo, con precisión de nanosegundos e inmutable.

use Time\Duration;

$timeout = Duration::fromMinutes(1)->add(Duration::fromSeconds(30));
$medio = $timeout->divideBy(2);

Ya lo usan APIs nuevas como Io\Poll\Context::wait(). Fuente: RFC duration_class.

3. clamp()

Adiós a max($min, min($max, $valor)).

$porcentaje = clamp($porcentaje, min: 0, max: 100);
clamp(4, 8, 6); // ValueError: $min mayor que $max

Si $min o $max son NAN, lanza ValueError. Útil para normalizar columnas numéricas antes de guardarlas. Fuente: RFC clamp_v2.

4. Valores por defecto en propiedades readonly

Una propiedad readonly puede declarar su valor inicial en la propia declaración, lo que permite cumplir hooks de interfaz con un valor fijo.

interface Regla {
    public string $parser { get; }
}

final readonly class ReglaCsv implements Regla {
    public string $parser = CsvParser::class;
}

Fuente: RFC readonly_property_defaults.

5. #[\Override] en constantes y casos de enum

El atributo ya no se limita a métodos: si la constante no existe en el padre o la interfaz, error en tiempo de compilación.

class Modelo implements TieneVersion {
    #[\Override]
    const VERSION = '2';
}

Fuente: RFC override_constants.

6. Errores JSON con línea y columna

Al decodificar, el mensaje indica dónde está el fallo, tanto en json_last_error_msg() como en la JsonException de json_decode() con JSON_THROW_ON_ERROR.

json_decode('{"name" "value"}');
echo json_last_error_msg(); // Syntax error near location 1:9

Para quien importa JSON de terceros, esto ahorra muchos substr de depuración. Los errores de json_encode() no llevan ubicación. Fuentes: UPGRADING (sección JSON) y ext/json/json.c.

7. Errores de stream como excepciones

Nuevo enum StreamErrorMode con tres casos: Error (el comportamiento de siempre, por defecto), Exception y Silent. Se configura por contexto.

$ctx = stream_context_create(['stream' => [
    'error_mode' => StreamErrorMode::Exception,
]]);

try {
    fopen('/no/existe', 'r', false, $ctx);
} catch (StreamException $e) {
    $errores = $e->getErrors();
}

También llegan stream_last_errors() y stream_clear_errors(). Fuente: RFC stream_errors.

8. Io\Poll

API de sondeo de eventos con backends nativos (epoll, kqueue y otros), pensada para bucles de eventos que hoy dependen de stream_select() o de una extensión.

use Io\Poll\{Context, Event};

$poll = new Context();
$poll->add(new StreamPollHandle($server), [Event::Read]);
$listos = $poll->wait(Time\Duration::fromSeconds(1));

wait() recibe un ?Time\Duration. Fuente: RFC poll_api y el stub ext/standard/io_poll.stub.php.

9. enum SortDirection

Un enum global con Ascending y Descending, para dejar de pasar 'asc' y 'desc' como strings.

$query->orderBy('created_at', SortDirection::Descending);

Ninguna función nativa lo recibe todavía; es una base común para librerías y frameworks (el ejemplo asume que tu capa de consultas lo acepta). Fuente: RFC sort_direction_enum.

10. UriBuilder

La extensión URI de PHP 8.5 permitía parsear; ahora también construir, con Uri\Rfc3986\UriBuilder y Uri\WhatWg\UrlBuilder.

$uri = new Uri\Rfc3986\UriBuilder()
    ->setScheme('https')
    ->setHost('vitor.pro')
    ->setPath('/blog')
    ->build();

Fuente: RFC uri_followup y UPGRADING.

11. array_map() compilado a foreach

Con un first-class callable o una aplicación parcial como callback, el compilador convierte array_map() en el bucle equivalente.

$limpias = array_map(trim(...), $filas);

Según zend_compile_func_array_map() en Zend/zend_compile.c, solo ocurre si el archivo no usa declare(strict_types=1), si se pasa un único array y si los argumentos fijados del parcial son literales. Muchos proyectos Laravel declaran strict_types, así que conviene comprobarlo antes de esperar mejoras. No hay cifras oficiales: mide tu propio pipeline. Fuente: UPGRADING (sección Performance).

12. JIT en builds ZTS sobre Apple Silicon

OPcache admite JIT en builds thread safe (ZTS) en Macs con Apple Silicon. Además, UPGRADING indica que el rendimiento de los builds ZTS ha mejorado, sin dar cifras.

opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=64M

Fuente: UPGRADING (secciones Opcache y Performance).

13. Sesiones seguras por defecto

Cambian tres valores por defecto:

session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"

Laravel gestiona sus propias sesiones y no usa ext/session, pero cualquier código PHP nativo o legado que dependa de $_SESSION sí lo nota. Fuente: RFC session_security_defaults.

Qué revisar al migrar

  • Sesiones nativas: con SameSite=Lax, las cookies de sesión dejan de enviarse en peticiones POST entre sitios (flujos de pago o SSO que vuelven por POST). Con use_strict_mode=1, se rechazan los IDs de sesión que no generó el servidor. Si hace falta, fija los valores antiguos de forma explícita en tu php.ini.
  • Errores de stream: el modo por defecto sigue siendo Error. Si activas Exception en un contexto, revisa los sitios que comprobaban false tras fopen() o file_get_contents().
  • Mensajes JSON: si tus tests comparan el texto exacto de json_last_error_msg() o de JsonException, ahora incluye «near location».
  • Nombres globales nuevos: clamp(), SortDirection, StreamErrorMode, StreamException y StreamPollHandle viven en el espacio global. Busca colisiones con funciones o clases propias sin namespace.
  • array_map y strict_types: la optimización no se aplica en archivos con strict_types=1; no cambia el resultado, solo el rendimiento esperado.
  • Lista completa: lee la sección de cambios incompatibles de UPGRADING antes de actualizar producción, y prueba con la RC en CI.
End of article