Publicado el Actualizado el Qué cambió

PHP 8.6: qué se rompe de verdad al actualizar

Foto de Marco Orta | 11 min de lectura
Ilustración 3D del elefante de PHP junto a un escudo agrietado, que representa los defaults de seguridad que cambian en PHP 8.6
Tabla de Contenidos

    PHP 8.6 sale el 19 de noviembre de 2026, y lo que más probablemente te va a tumbar producción no es ninguna función eliminada — es que cambian los defaults de tres directivas de sesión. session.use_strict_mode, session.cookie_httponly y session.cookie_samesite cambian de valor, y el modo de falla es silencioso: nada lanza excepción, tus usuarios simplemente dejan de mantener la sesión iniciada en peticiones cross-site.

    Todo lo demás son deprecaciones, es decir, avisos y no errores fatales. Pero hay cuatro cambios de comportamiento genuinos escondidos entre ellas, y dos de esos cambian el resultado sin decir una palabra.

    Esta es la lista de rupturas. Si buscas las novedades — Partial Function Application, clamp(), el enum SortDirection — están en todas las novedades de PHP 8.6.

    Qué tan cerrada está esta lista

    Vale la pena aclararlo, porque los artículos de “qué rompe PHP 8.6” empezaron a publicarse antes de que hubiera algo que reportar.

    El calendario es público: la alpha 1 salió el 2 de julio, la beta 1 con el soft feature freeze llegó el 13 de agosto y el hard feature freeze pasó el 22 de septiembre. No hubo RC1: un error al empaquetarla (no se subieron los números de versión de la API al crear la rama PHP-8.6) hizo que la primera release candidate saliera como RC2, el 24 de septiembre. La RC3 salió el 8 de octubre, la RC4 está planeada para el 22 de octubre y la RC5 para el 5 de noviembre. La disponibilidad general sigue siendo el 19 de noviembre de 2026.

    Lo que eso significa para este artículo:

    • La lista ya está prácticamente cerrada. Pasamos el hard freeze, así que de aquí en adelante solo entran correcciones de bugs, y solo de vez en cuando una añade una nota de actualización.
    • Las deprecaciones ya están cerradas. Vienen de RFCs que ya se votaron, y una RFC votada no se desvota.
    • Ya hay página oficial de migración. php.net/manual/es/migration86.php se publicó después de la primera versión de este artículo; la otra fuente de verdad es el archivo UPGRADING en la rama PHP-8.6 de php-src, de donde sale todo lo de abajo.

    Estado al 15 de octubre de 2026: verificado contra la RC3 (8 de octubre) y el UPGRADING de la rama PHP-8.6. El cambio de defaults de sesión sigue dentro, sin cambios. La lista sí creció un poco después del freeze — sobre todo deprecaciones que se votaron antes pero se documentaron hasta el 28 de septiembre — y ya están incluidas abajo.

    La que sí va a tumbar producción

    Los defaults de seguridad de sesión cambian

    Tres valores de php.ini cambian:

    DirectivaAntesEn PHP 8.6
    session.use_strict_mode01
    session.cookie_httponly01
    session.cookie_samesite"""Lax"

    Cada uno por separado es el default correcto. Juntos son el cambio más disruptivo de la versión, porque ninguno de los tres lanza un error. Tu aplicación sigue corriendo y algunos usuarios simplemente dejan de estar logueados.

    cookie_samesite = "Lax" es el que muerde. Una cookie Lax no viaja en peticiones POST cross-site. Si algo le hace POST a tu aplicación desde otro origen — una pasarela de pago que regresa al usuario por POST, el callback de un proveedor de identidad, un formulario embebido, un webhook que depende de la sesión — esa petición ahora llega sin sesión. El usuario aparece en la pantalla de login sin ninguna razón visible.

    cookie_httponly = 1 significa que JavaScript ya no puede leer la cookie de sesión con document.cookie. Es lo correcto, y rompe cualquier código de front-end que leyera el ID de sesión directamente — algunos snippets viejos de analítica y auth por AJAX hecho a mano hacen justo eso.

    use_strict_mode = 1 hace que PHP rechace IDs de sesión que él mismo no generó, que es la corrección real contra la fijación de sesión (session fixation). Rompe flujos que meten un ID de sesión desde afuera.

    Si necesitas el comportamiento anterior de forma temporal, fíjalo explícitamente en vez de depender del default previo:

    ; solo como parche mientras arreglas la causa real
    session.cookie_samesite = "None"
    session.cookie_secure = 1   ; obligatorio siempre que uses SameSite=None
    

    Ojo: los navegadores rechazan SameSite=None sin Secure, así que esto no es un revert de una línea.

    El consejo honesto: no lo reviertas. Fija estas tres directivas explícitamente en tu php.ini hoy mismo, en PHP 8.5, y averigua qué se rompe mientras todavía controlas cuándo. Eso convierte una sorpresa del día del lanzamiento en una tarde de martes cualquiera.

    Los cambios de comportamiento que no hacen ruido

    trim() ahora elimina el form feed

    trim(), ltrim() y rtrim() agregan \f (form feed, 0x0C) a su lista de caracteres por defecto. Era el único carácter de espacio en blanco ASCII que no eliminaban.

    trim("hola\f");
    // PHP 8.5 → "hola\f"
    // PHP 8.6 → "hola"
    

    Casi siempre es lo que querías. Lo listo aquí porque cambia la salida en silencio, y si tienes pruebas que comparan cadenas exactas al parsear archivos de ancho fijo, streams de impresión viejos o cualquier cosa que arrastre form feeds, van a empezar a fallar sin causa obvia.

    Los NUL bytes ahora lanzan en vez de tolerarse

    Un grupo grande de funciones ahora lanza ValueError cuando reciben una cadena con un byte NUL, en vez de truncar o comportarse de forma impredecible. La lista incluye getenv(), putenv(), parse_str(), setlocale(), dl(), openlog(), proc_open() (el argumento $cwd), y cerca de veinte funciones de sistema de archivos — file_exists(), is_file(), filesize(), stat() y sus parientes.

    Es un endurecimiento de seguridad: los NUL bytes en esos argumentos han sido históricamente un vector de path traversal. El impacto práctico es que el código que pasa entrada de usuario sin sanear a validaciones de archivos ahora recibe una excepción ruidosa en vez de un silencio sin sentido:

    // PHP 8.5: devuelve false, sin ninguna señal
    // PHP 8.6: lanza ValueError
    file_exists($_GET['path']);   // cuando $_GET['path'] contiene "\0"
    

    Si tu aplicación hace esto, la excepción te está avisando de un bug que ya existía.

    unpack() reinterpreta < y >

    unpack() ahora trata < y > después de un código de formato como modificadores de endianness, no como parte del nombre. Esto cambia cómo se parsean cadenas de formato que ya tenías:

    unpack("s<valor", $data);
    // PHP 8.5 → clave "<valor"
    // PHP 8.6 → short little-endian, clave "valor"
    
    unpack("C>nombre", $data);
    // PHP 8.6 → lanza excepción (C no tiene endianness)
    

    Es un caso puntual, pero si parseas formatos binarios es una ruptura dura, no una deprecación. Busca unpack( en tu código y revisa si algún formato tiene < o > dentro de un nombre.

    SessionHandlerInterface pide dos métodos más

    Los manejadores de sesión personalizados que no implementan create_sid() ni validateId() ahora emiten deprecaciones. Si escribiste a mano un manejador de sesiones en base de datos o Redis, lo más probable es que implemente los seis métodos originales y no estos dos — y validateId() es justo lo que hace funcionar de verdad a use_strict_mode, así que este punto se conecta directo con el cambio de arriba.

    Las deprecaciones

    Ninguna de estas es fatal en 8.6. Emiten E_DEPRECATED, y son la lista de eliminación para PHP 9.

    return dentro de finally. Regresar un valor desde un bloque finally descarta cualquier valor de retorno o excepción del try, que casi siempre está tapando un bug. Ahora está deprecado.

    function f() {
        try { return 1; }
        finally { return 2; }   // deprecado — gana en silencio, regresa 2
    }
    

    Regresar un valor desde un constructor. Ya está cubierto con detalle en el post de novedades, pero la versión corta: es deprecación en tiempo de compilación, la ves aunque el código nunca se ejecute, y pasó la votación 39 a 0.

    array_filter() lanza excepción con un $mode inválido. Antes ignoraba en silencio un modo incorrecto; ahora lanza ValueError. Es una mejora estricta, pero puede tumbar código que llevaba años pasando una constante equivocada sin que nadie se diera cuenta.

    php://filter limita los filtros encadenados. Endurecimiento contra una técnica conocida de explotación por cadenas de filtros. Solo te toca si construyes cadenas de filtros a propósito.

    Todo mbregex. Cada función mb_ereg* queda deprecada, porque Oniguruma, la librería detrás de ellas, dejó de tener mantenimiento. Es la deprecación más grande de la versión en superficie de código. La migración es a PCRE — preg_match() y compañía, con el modificador u para Unicode. El post de novedades trae el detalle completo de las 14 funciones y el checklist de migración; aquí basta con saber que no es opcional para siempre: se elimina en PHP 9.

    Alias de verificación y conversión de tipos. is_double(), is_long(), is_integer() y doubleval() quedan deprecadas a favor de is_float(), is_int() y floatval(). También se deprecan metaphone(), strcoll(), spl_object_hash(), spl_classes() y la bandera de ordenamiento SORT_LOCALE_STRING.

    spl_object_hash() es la que vale la pena marcar: reemplázala por spl_object_id(), que casi seguro es lo que en realidad querías.

    Métodos de CSV en SPL y de ArrayIterator. SplFileObject::fgetcsv(), fputcsv(), setCsvControl() y getCsvControl() quedan deprecados, junto con diez métodos de ArrayIterator: getFlags(), setFlags(), asort(), ksort(), uasort(), uksort(), natsort(), natcasesort(), serialize() y unserialize().

    Argumentos de objeto donde nunca tuvieron sentido. array_walk(), mb_convert_variables(), http_build_query(), deflate_init() / inflate_init() y los filtros de stream de zlib y bz2 ahora deprecan argumentos tipo objeto. http_build_query() es la que más se usa en la vida real: pasarle un DTO o un objeto simple funcionaba, y ahora cada llamada avisa — conviértelo antes con get_object_vars().

    is_a() e is_subclass_of() con el nombre de la clase como cadena. Pasar una cadena como primer argumento con $allow_string en false queda deprecado. Nunca hizo lo que parecía; pasa el objeto, o pon $allow_string en true.

    Nombres que parecen reservados. Usar let o is como nombre de clase, interfaz, trait, enum, función o constante queda deprecado, igual que _ como nombre de constante, que extiende la deprecación de PHP 8.4 de _ como nombre de clase. Pasarle un tercer argumento a define() también queda deprecado.

    Cabos sueltos de mysqli. mysqli_get_charset() y mysqli_stmt_init() quedan deprecadas. La opción classmap de SOAP ahora rechaza claves enteras.

    Lo que entró tarde y sí rompe

    Dos rupturas duras, puntuales, entraron al UPGRADING después de la primera versión de este artículo, y las dos lanzan en vez de avisar:

    • ZipArchive ya no se puede serializar. serialize() y unserialize() sobre un ZipArchive ahora lanzan Exception. Si algo cachea o encola objetos que lo contienen, falla ahí.
    • ftp_nb_fget(), ftp_nb_fput(), ftp_nb_get() y ftp_nb_put() lanzan Error cuando la conexión ya está transfiriendo, en vez de emitir un warning y devolver false.

    Y una que solo toca el desarrollo local: el servidor integrado de la CLI ya no refleja la cabecera Host de la petición.

    Qué hacer en realidad, en orden

    1. Fija las tres directivas de sesión explícitamente en PHP 8.5, hoy. Es todo el riesgo de la versión concentrado en un solo cambio, y puedes probarlo hoy en 8.5 sin esperar a ninguna RC. Revisa cada POST cross-site que le llegue a tu aplicación.
    2. Activa el reporte de deprecaciones y corre tu suite de tests. error_reporting(E_ALL) con un log que de verdad revises. La mayor parte de esta versión aparece ahí.
    3. Busca los nombres específicos. mb_ereg, is_double, is_long, is_integer, doubleval, spl_object_hash, metaphone, strcoll, http_build_query, unpack(. Es una pasada de veinte minutos y cubre casi toda la lista de deprecaciones.
    4. Revisa tus manejadores de sesión personalizados en busca de create_sid() y validateId().
    5. No toques trim() a menos que falle alguna prueba. Si falla una, aprendiste algo sobre tus datos de entrada.

    Lo que NO se rompe

    Vale la pena decirlo, porque la ansiedad de actualizar rellena huecos que no existen. PHP 8.6 no elimina nada que estuviera deprecado en 8.x — las eliminaciones llegan hasta PHP 9. Tus propiedades tipadas, enums, clases readonly, fibers y atributos siguen intactos. Si tu aplicación corre limpia en 8.5 con las deprecaciones silenciadas, el peor caso realista para 8.6 es un log ruidoso más el cambio de sesión.

    Ese cambio de sesión, eso sí, no es un detalle menor. Es la razón por la que existe este artículo.

    Preguntas frecuentes

    ¿Cuándo se libera la versión estable de PHP 8.6? El 19 de noviembre de 2026. El hard feature freeze pasó el 22 de septiembre. La RC1 se saltó por un error de empaquetado, así que las release candidates son la RC2 (24 de septiembre), la RC3 (8 de octubre), la RC4 (22 de octubre) y la RC5 (5 de noviembre).

    ¿Cuál es el cambio más peligroso de PHP 8.6? Los defaults de sesión: session.use_strict_mode y session.cookie_httponly pasan a 1, y session.cookie_samesite pasa a "Lax". Fallan en silencio — los usuarios dejan de mantener sesión en POSTs cross-site en vez de recibir un error.

    ¿PHP 8.6 elimina funciones existentes? No. Deprecia muchísimo — todo mbregex, varios alias de verificación de tipos, métodos CSV de SPL — pero las eliminaciones están programadas para PHP 9. Las deprecaciones emiten avisos, no errores fatales.

    ¿De verdad va a desaparecer mb_ereg? Está deprecada en 8.6 porque Oniguruma, la librería detrás de ella, dejó de tener mantenimiento, pero todavía funciona. Migra a PCRE (preg_match y compañía con el modificador u). Los patrones no siempre se traducen uno a uno, así que conviene probar en vez de hacer un find-and-replace.

    Para seguir

    Cambios en este artículo

    • — Verificado contra la RC3 (8 de octubre) y el UPGRADING de la rama PHP-8.6: el cambio de defaults de sesión sigue igual. La RC1 se saltó (la primera release candidate fue la RC2, el 24 de septiembre) y las RC ahora van de la RC2 a la RC5; la GA sigue el 19 de noviembre. Se agregan las deprecaciones y rupturas documentadas después del hard freeze: let, is y _ como identificadores, is_a()/is_subclass_of() con cadena, http_build_query() con objeto, la serialización de ZipArchive y las funciones ftp_nb_*. La página oficial de migración ya existe.
    ¿Te sirvió? Compártelo

    ¿Te sirvió? Recibe la próxima por correo

    Una vez por semana: qué se rompe al actualizar, IA para desarrolladores y lo que estoy construyendo, con fuentes. Sin spam.

    Al suscribirte aceptas nuestro aviso de privacidad.

    Buscar

    Etiquetas

    IA Migración PHP Laravel JavaScript Tutorial Seguridad Desarrollo Web Upgrade Buenas Prácticas Agentes MCP OpenAI SEO Backend