PHP 8.6: novedades, y qué gana realmente al actualizar desde 7.x o las primeras 8.x
PHP 8.6 todavía no ha salido. A día de hoy está en Beta 3, publicada el 10 de septiembre, con la RC1 prevista para el 24 de septiembre y la versión final en el calendario de los release managers para el 19 de noviembre de 2026. La congelación de funcionalidades fue la Beta 1, el 13 de agosto, así que lo que se describe abajo es lo que se publicará; lo que cambia de aquí a noviembre son correcciones de errores. El anuncio de la beta dice que no se use en producción, y estamos de acuerdo.
Aun así quedan dos preguntas que nos hacen cada vez que llega un PHP nuevo. Qué trae, y qué gana realmente una tienda o una aplicación que sigue en 7.4, 8.0 u 8.1 al pasarse. La segunda suele responderse con cifras copiadas del benchmark de otro, así que esta vez ejecutamos el nuestro.
Novedades
El archivo UPGRADING de la Beta 3 ronda las mil líneas. La mayor parte es limpieza a nivel de extensión: una función que ahora lanza un ValueError donde antes devolvía false. Estos son los cambios que notará la mayoría del código de aplicación.
Aplicación parcial de funciones
Puede dejar un hueco en una llamada a función con ? y recibir un closure que lo rellena:
// PHP 8.5 and earlier
$result = array_map(static fn(string $s): string => str_replace('hello', 'hi', $s), $items);
// PHP 8.6
$result = array_map(str_replace('hello', 'hi', ?), $items);
... significa "todos los argumentos restantes", así que foo(1, ...) es un closure sobre todo lo que viene después del primer parámetro. El closure que construye el motor conserva los nombres, tipos y valores por defecto reales de la función subyacente, de modo que la reflexión y el análisis estático ven lo que esperaría. Es la continuación de la sintaxis de first-class callable de 8.1 (strlen(...)), que el RFC describe como el caso degenerado de esta. Encaja bien junto al operador pipe de 8.5.
Valores por defecto en propiedades readonly
public readonly string $driver = 'redis'; era un error de compilación hasta ahora. El RFC original de readonly lo descartó porque una propiedad readonly con valor por defecto es en la práctica una constante. Las propiedades de interfaz de 8.4 cambiaron el cálculo: una clase ahora puede satisfacer public string $name { get; } con un valor readonly fijo y sin código repetitivo en el constructor.
Time\Duration
Una clase nueva, siempre disponible, para tiempo de cronómetro —timeouts, back-off, "ejecutar cada 30 segundos"— con precisión de nanosegundos y sin la ambigüedad de calendario de DateInterval. Duration::fromMilliseconds(500), fromSeconds(), fromMinutes(), fromHours(), además de parseo ISO 8601. Espere que las bibliotecas que hoy reciben int $timeoutSeconds empiecen a aceptar una de estas.
Cambios menores en el lenguaje
#[\Override]ahora funciona en constantes de clase y en casos de enum, así que una errata en el nombre de una constante sobrescrita es un error de compilación en lugar de una segunda constante.- Los enums pueden definir
__debugInfo(). - Los objetos guardados en constantes admiten escritura directa de propiedades (
Foo::BAR->prop = $val). SortDirectiones un enum nuevo para las funciones de ordenación.trim(),ltrim()yrtrim()ahora eliminan el salto de página (\f) por defecto. Pequeño, y es un cambio de comportamiento.- Los mensajes de
json_last_error_msg()y deJsonExceptionahora indican en qué punto del documento está el error. - Una opción INI nueva,
error_include_args, hace que los mensajes de error muestren los argumentos que realmente se pasaron, con las mismas reglas de redacción que los stack traces. Desactivada por defecto.
Para quien escribe servidores y clientes
Tres de los RFC más grandes son infraestructura que le llegará a través de bibliotecas, no en su propio código: una API de errores de stream (objetos StreamError tipados en lugar de avisos, controlada por opciones del contexto de stream), una API de polling bajo Io\Poll para esperar sobre muchos handles a la vez, y reanudación de sesión TLS, PSK externo y early data de TLS 1.3 en sockets de stream. También hay un par UriBuilder/UrlBuilder para la extensión URI que llegó en 8.5, y un lote de opciones de socket —keepalive, linger, tamaños de buffer— que antes requerían la extensión sockets.
Valores de sesión que ahora son seguros
Este es el cambio de mayor alcance práctico, y está en la sección de incompatibilidades por una razón. Cambian tres valores INI por defecto:
| Opción | Antes | Ahora |
|---|---|---|
session.use_strict_mode |
0 | 1 |
session.cookie_httponly |
0 | 1 |
session.cookie_samesite |
sin definir | Lax |
El modo estricto rechaza un ID de sesión que el servidor nunca emitió, lo que cierra la fijación de sesión. HttpOnly mantiene la cookie fuera del alcance de document.cookie. Lax impide que se envíe en POST entre sitios.
Si está en Laravel o Magento, el framework ya configura los tres, así que no cambia nada. Dónde afecta: un manejador de guardado de sesión personalizado que nunca implementó validateId() y create_sid() —ahora se esperan, y pasar un manejador sin ellos está deprecado por separado— y cualquier cosa que lea la cookie de sesión desde JavaScript. Un checkout que vuelve con un POST desde la página de un proveedor de pagos en otro dominio y espera que llegue la cookie de sesión necesita SameSite=None definido explícitamente, con cookie_secure activado.
Qué queda deprecado
Las deprecaciones son avisos. Nada deja de funcionar en 8.6; la cuestión es que dejará de funcionar en 9.0. Las que verá en una base de código real, más o menos por frecuencia:
spl_object_hash(): use spl_object_id(). Está en todas partes. En el directorio vendor/ de una aplicación Laravel 13 que mantenemos aparece en 15 archivos de 12 paquetes, entre ellos laravel/framework, symfony/console, guzzlehttp/guzzle y nesbot/carbon. Cada uno es un aviso en 8.6 hasta que el paquete se actualice, y todos se actualizarán.
return dentro de un bloque finally: descarta en silencio la excepción o el valor de retorno del try, algo que ha sido una trampa desde PHP 5.5. Ahora se lo avisan.
is_integer(), is_long(), is_double(), doubleval(): los alias. Use is_int(), is_float(), floatval(). El código antiguo de extensiones de Magento usa is_integer() con bastante frecuencia.
La familia mb_ereg: la biblioteca de expresiones regulares Oniguruma que hay debajo ya no se mantiene, así que todo mbregex queda deprecado. Si tiene mb_ereg_replace() en una rutina de slugs o de normalización de búsquedas, pásela a preg_replace() con el modificador /u.
Devolver un valor desde __construct() o __destruct(), y convertir cualquiera de los dos en generador.
SplFileObject::fgetcsv(), fputcsv(), setCsvControl(), getCsvControl(): los métodos CSV de SplFileObject. Los scripts de importación construidos sobre ellos deberían pasar a fgetcsv() sobre un stream normal.
ArrayIterator::asort(), ksort(), getFlags(), setFlags() y el resto de métodos que heredaba de ArrayObject.
mysqli_stmt_init(), mysqli_get_charset(), metaphone(), spl_classes(), y pasar un objeto donde se espera un array a array_walk(), http_build_query() y mb_convert_variables().
La lista completa con el razonamiento está en el RFC de deprecaciones. Ejecute su suite de tests sobre la RC con error_reporting=E_ALL y haga grep en el log; esa es toda la auditoría.
Qué se ha acelerado dentro de 8.6
La sección de rendimiento del archivo UPGRADING lista cambios a nivel de compilador:
- Las llamadas a
printf()que solo usan%sy%dse compilan como interpolación de cadenas, así que no hay llamada a función ni parseo de la cadena de formato. array_map()con un first-class callable o una aplicación parcial se compila como elforeachequivalente, lo que elimina la asignación del closure y la sobrecarga de llamar a código de usuario desde una función interna.- Los closures que demostrablemente no usan
$thisse convierten en estáticos automáticamente, lo que rompe el ciclo de referencias objeto-closure que de otro modo se queda esperando al recolector de basura. Los closures sin estado se cachean en lugar de recrearse en cada llamada. El RFC probó la inferencia sobre la demo de Symfony y detectó 68 de los 87 closures que estaban marcados explícitamente como static. - La codificación JSON de arrays y objetos es más rápida, y en particular la indentación del pretty-print.
- La VM de tail-call es más rápida, la compilación ZTS es más rápida, y el JIT ahora funciona en compilaciones ZTS sobre Apple Silicon, lo que importa para los Mac que ejecutan la versión con hilos.
Un grupo de funciones de la biblioteca estándar —array_sum(), array_product(), array_intersect(), array_unshift(), array_walk(), str_split()— también recibió mejoras individuales. Ninguna viene con una cifra, y en nuestra medición de abajo la diferencia entre 8.5 y 8.6 queda dentro del ruido. Son reales, y son pequeñas.
Qué gana realmente al actualizar
Esta es la parte que se copia de un sitio a otro sin fuente. Las cifras propias de php.net se detienen en 8.1: la página de la versión 8.0 dice que el JIT tracing muestra "alrededor de 3 veces mejor rendimiento en benchmarks sintéticos y una mejora de 1,5–2 veces en algunas aplicaciones específicas de larga ejecución", y que "el rendimiento de una aplicación típica está a la par con PHP 7.4". La página de 8.1 reporta la aplicación de demostración de Symfony un 23,0% más rápida y WordPress un 3,5% más rápido que en 8.0, sobre todo por la caché de herencia, que dejó de volver a enlazar las clases en cada petición. A partir de 8.2 las páginas de versión dicen "mejoras de rendimiento" y no dan cifras.
Así que lo medimos. Misma máquina, mismo código, ocho versiones de PHP.
Configuración. Un portátil con Intel Core i9, Docker bajo Colima con 8 CPU, las imágenes oficiales php:<version>-cli: 7.4.33, 8.0.30, 8.1.34, 8.2.33, 8.3.33, 8.4.25, 8.5.10 y 8.6.0beta3. Opcache activado en todas las ejecuciones. Cada versión medida dos veces: con el JIT desactivado, y después con el JIT tracing y un buffer de 64 MB. Cada cifra es la mediana de ejecuciones repetidas, y las versiones se intercalaron para que un portátil que se va calentando no penalizara a la que tocara ejecutar la última.
Benchmark uno: Zend/bench.php. El micro-benchmark propio de PHP: bucles cerrados, recursión, construcción de cadenas. Segundos totales, menos es mejor, mediana de siete:
| PHP | JIT desactivado | JIT activado |
|---|---|---|
| 7.4.33 | 0.242 | — |
| 8.0.30 | 0.247 | 0.106 |
| 8.1.34 | 0.265 | 0.104 |
| 8.2.33 | 0.258 | 0.103 |
| 8.3.33 | 0.258 | 0.101 |
| 8.4.25 | 0.257 | 0.100 |
| 8.5.10 | 0.264 | 0.099 |
| 8.6.0beta3 | 0.268 | 0.100 |
Eso es el "3 veces en benchmarks sintéticos" de php.net: nosotros obtuvimos 2,4× de 7.4 a 8.6 con el JIT. También es toda la historia de esa cifra: con el JIT desactivado, este benchmark es plano de 7.4 a 8.6. El intérprete en sí no se volvió más rápido en bucles cerrados, y si acaso la línea 8.x es unos milisegundos más lenta aquí. No investigamos por qué.
Benchmark dos: una página que se parece a una aplicación. Nadie sirve bench.php. Así que escribimos una página pequeña con autoload: una jerarquía de clases de cinco niveles con unas 55 clases, un contenedor, un despachador de eventos, un catálogo de 3.000 objetos producto filtrados y ordenados en un listado, un carrito valorado con aritmética monetaria en enteros y un descuento por grupo de cliente, un payload codificado y decodificado a JSON cinco veces, slugs con preg_replace, formato con DateTimeImmutable, y una plantilla .phtml renderizada mediante output buffering con escapado. Unos 10 ms de PHP por petición en 7.4, y salida idéntica byte a byte en todas las versiones. Servida por el servidor integrado de PHP, atacada secuencialmente con ab. Milisegundos por petición, menos es mejor, mediana de tres ejecuciones de 600 peticiones:
| PHP | JIT desactivado | JIT activado |
|---|---|---|
| 7.4.33 | 9.94 | — |
| 8.0.30 | 9.65 | 8.49 |
| 8.1.34 | 9.42 | 8.23 |
| 8.2.33 | 9.34 | 7.83 |
| 8.3.33 | 9.33 | 7.93 |
| 8.4.25 | 9.51 | 8.33 |
| 8.5.10 | 9.54 | 7.74 |
| 8.6.0beta3 | 9.54 | 7.73 |
Cómo leerlo:
- De 7.4 a 8.6, JIT desactivado: alrededor de un 4% menos de tiempo por petición. Eso es "a la par", como dijo php.net en 2020.
- De 7.4 a 8.6 con el JIT activado: alrededor de un 22% menos de tiempo, o aproximadamente 1,3× el rendimiento. Más que el "a la par" de php.net para aplicaciones típicas, y muy lejos del 2,4× que muestra el benchmark sintético.
- De 8.0 a 8.6, ambas con el JIT: alrededor de un 9%. De las primeras 8.x a la actual es una ganancia modesta, repartida en seis versiones.
- Las versiones 8.x adyacentes quedan dentro del ruido. La variación entre ejecuciones en un portátil es del 5–10%. La bajada de 8.4 y la subida de 8.2 en la columna del JIT no son reales; no construya un argumento sobre ellas.
Dos advertencias honestas. Nuestra página tiene 55 clases; una petición de Magento o de Symfony enlaza cientos, que es exactamente donde la caché de herencia de 8.1 dio fruto y de donde salió el 23% de php.net. Si su aplicación depende mucho de un framework, el salto de 8.0 a 8.1 probablemente sea mayor para usted de lo que muestra esta tabla. Y la sobrecarga del servidor integrado más el loopback es un coste fijo dentro de cada cifra, lo que comprime ligeramente las proporciones; bajo PHP-FPM los porcentajes serían algo mayores, no menores.
El JIT sigue desactivado por defecto, en 8.6 igual que en todas las versiones desde 8.0. De 8.0 a 8.3 el tamaño del buffer era cero por defecto; desde 8.4 opcache.jit es disable por defecto. Activarlo son dos líneas de php.ini:
opcache.jit=tracing
opcache.jit_buffer_size=64M
Es reversible, así que lo correcto es medir su propia aplicación en staging con él activado y desactivado. La nuestra ganó una quinta parte. La suya puede ganar menos, y una página muy dependiente de la base de datos ganará casi nada porque el tiempo no está en PHP.
La verdadera razón para actualizar
No es la velocidad. De las páginas de versiones con soporte y de fin de soporte de php.net:
| Rama | Fin de las correcciones de seguridad |
|---|---|
| 7.4 | 28 de noviembre de 2022 |
| 8.0 | 26 de noviembre de 2023 |
| 8.1 | 31 de diciembre de 2025 |
| 8.2 | 31 de diciembre de 2026 |
| 8.3 | 31 de diciembre de 2027 |
| 8.4 | 31 de diciembre de 2028 |
| 8.5 | 31 de diciembre de 2029 |
| 8.6 | 31 de diciembre de 2030, con la misma política de cuatro años |
Un servidor en 7.4 lleva casi cuatro años sin correcciones de seguridad. 8.0 y 8.1 están en la misma situación. A 8.2 le quedan quince semanas. Ese es el argumento para moverse, y una página un 4% o un 22% más rápida es lo que se lleva de propina.
Dónde aterriza depende de lo que tenga encima de PHP. Magento 2.4.9 soporta PHP 8.4 y 8.5, y Adobe ha añadido cada versión nueva de PHP en la siguiente versión de funcionalidades —8.3 con 2.4.7, 8.4 con 2.4.8, 8.5 con 2.4.9—, así que 8.6 en Magento es una cuestión de 2.4.10 y no de noviembre. Mage-OS sigue la misma línea. Laravel publica una tabla de soporte por versión; consúltela antes de mover un servidor, porque la restricción es el rango del framework, no el de PHP. Si sigue en Magento 1, OpenMage LTS funciona de 8.1 a 8.5. Y sea cual sea el destino, la actualización de PHP es un paso propio, hecho y verificado antes de la actualización de la aplicación, para que un fallo tenga una causa posible en lugar de dos.
Lo que haríamos hoy con la beta: ejecutar su suite de tests contra php:8.6.0beta3-cli o la RC, con error_reporting=E_ALL, y contar las deprecaciones por paquete. Es una tarde, le dice a qué proveedores está esperando, y es la única parte de la actualización a 8.6 que merece la pena empezar antes de noviembre.
Emyrix realiza actualizaciones de PHP y de plataforma para aplicaciones Magento, Adobe Commerce, Mage-OS y Laravel. Si quiere la auditoría de deprecaciones hecha sobre su base de código o una actualización de PHP planificada, contáctenos. Para una tienda Magento, el diagnóstico gratuito le dirá en qué versión está y qué le está costando.
Las listas de funcionalidades, las deprecaciones y el calendario de publicación proceden del archivo UPGRADING de PHP 8.6.0 Beta 3, de los RFC enlazados arriba y de la página de los release managers en wiki.php.net, todos leídos el 17 de septiembre de 2026. Las cifras de rendimiento de 8.0 y 8.1 son las propias de php.net. Las dos tablas de benchmarks son mediciones nuestras en un único portátil Intel y pretenden mostrar la dirección, no predecir su aplicación; el método está descrito completo arriba para que pueda repetirlo.
Preguntas frecuentes
¿Ya se ha publicado PHP 8.6?
No, a fecha de 17 de septiembre de 2026. La tercera beta salió el 10 de septiembre, la primera release candidate está prevista para el 24 de septiembre y la versión final está programada para el 19 de noviembre de 2026. La congelación de funcionalidades fue en la Beta 1, el 13 de agosto, así que la lista está cerrada; lo que cambia de aquí a noviembre son correcciones de errores.
¿Cuánto más rápido es PHP 8 que PHP 7.4?
Menos de lo que dicen la mayoría de los artículos. La propia afirmación de php.net para PHP 8.0 fue que el rendimiento de una aplicación típica está a la par con 7.4, y que el JIT da 3× en benchmarks sintéticos. Nuestra propia medición en una página con forma de aplicación coincide: de 7.4 a 8.6 es alrededor de un 4% más rápido sin el JIT y alrededor de un 22% con él. La caché de herencia de PHP 8.1 dio a los frameworks con grafos de clases grandes un salto mayor: php.net midió un 23% en la aplicación de demostración de Symfony.
¿El JIT de PHP viene activado por defecto en PHP 8.6?
No. Ha estado desactivado por defecto en todas las versiones desde 8.0, y 8.6 no lo cambia. Activarlo son dos líneas de php.ini —opcache.jit=tracing y opcache.jit_buffer_size=64M— y es reversible, así que lo sensato es medir su propia aplicación en staging con él activado y desactivado.
¿PHP 8.6 romperá mi aplicación Magento o Laravel?
Los cambios en tiempo de ejecución son en su mayoría avisos de deprecación, que no rompen nada pero sí llenan los logs. Lo que decide si puede ejecutarlo es la matriz de soporte de su framework, no PHP en sí. Magento 2.4.9 lista PHP 8.4 y 8.5; Adobe ha añadido cada versión nueva de PHP en la siguiente versión de funcionalidades, así que espere a su confirmación. Consulte la tabla de soporte de Laravel por la misma razón antes de mover un servidor.
¿Cuáles son los cambios de sesión de PHP 8.6?
Cambian tres valores INI por defecto: session.use_strict_mode pasa a 1, session.cookie_httponly pasa a 1 y session.cookie_samesite pasa a Lax. La mayoría de los frameworks ya los configuran por su cuenta, así que la mayoría de las aplicaciones no notarán diferencia. Lo que se rompe es un manejador de sesión personalizado que dependía de aceptar IDs de sesión suministrados desde fuera, o JavaScript que lee la cookie de sesión.
¿Qué versiones de PHP siguen teniendo soporte en 2026?
PHP 8.2, 8.3, 8.4 y 8.5. PHP 8.2 recibe solo correcciones de seguridad hasta el 31 de diciembre de 2026, así que es la siguiente que hay que dejar. 8.1 terminó el 31 de diciembre de 2025, 8.0 en noviembre de 2023 y 7.4 en noviembre de 2022.