← Blog

PHP 8.5 a los seis meses: lo que hemos aprendido aplicándolo en clientes reales

PHP 8.5 lleva en producción desde noviembre. Hacemos balance de lo que sí está marcando diferencia, lo que sigue siendo postureo y qué romper antes de pulsar el botón de actualización.

Lo que prometía y lo que ha dado

PHP 8.5 salió el 20 de noviembre de 2025. Llevamos seis meses con ella desplegada en clientes (algunos a la primera semana, otros en los últimos refrescos de marzo), así que ya hay datos en lugar de demos.

El resumen rápido: es la mejor versión menor de la serie 8.x para actualizar sin sustos. Más estable de salida que 8.3, menos disruptiva que 8.4 a nivel de breaking changes, y con un par de features que sí están entrando en código real, no solo en tweets.

Vamos a ver una a una qué hemos usado, qué no y qué romper antes de migrar.

Lo que sí está entrando en código de cliente

El operador pipe |>

El cambio que más nos ha sorprendido. Sobre el papel parecía azúcar sintáctico para pajearse en RFC; en código real está sustituyendo cadenas de array_map anidadas que costaba leer.

Antes:

$nombre = strtoupper(trim(str_replace('-', ' ', $slug)));

Después:

$nombre = $slug
    |> fn($s) => str_replace('-', ' ', $s)
    |> trim(...)
    |> strtoupper(...);

No es revolucionario. Pero en pipelines de validación, normalización de inputs y construcción de payloads para APIs se lee mejor, y los autores de los próximos PRs que entren al proyecto no tienen que descifrar paréntesis anidados. Lo estamos adoptando en código nuevo; el viejo lo dejamos en paz porque refactorizar por placer es la enfermedad más cara de la profesión.

clone with

Para clases readonly (que llevamos abusando desde 8.2 sin remordimiento), clone with resuelve el patrón "construye un nuevo objeto con un solo campo distinto":

$pedido_v2 = clone $pedido with { estado: 'confirmado' };

Sin esto, había que escribir un método withEstado() en cada clase o pasar por un constructor con N argumentos. Esto sí lo estamos metiendo en código existente, sobre todo en DTOs y value objects. Donde había withX(), ahora clone with.

#[\NoDiscard]

El que más nos gusta y menos publicidad ha tenido. Es un atributo que marca una función como "el valor de retorno importa, no lo tires":

#[\NoDiscard("La transacción puede haber fallado")]
public function ejecutar(): Resultado { ... }

Si alguien llama a $tx->ejecutar(); ignorando el retorno, PHP avisa en error_reporting. Esto es oro para librerías de pagos, locks, queues y todo lo que pueda fallar silenciosamente. Lo estamos metiendo en los métodos críticos de los servicios financieros de Atellum (TPV interno) — los bugs típicos de "llamé al método y no comprobé el resultado" desaparecen casi solos.

Extensión URI

Hasta ahora, parsear URLs en PHP era un mosaico de parse_url(), urlencode() y rezar. La nueva extensión URI implementa RFC 3986 y el WHATWG URL Standard:

$url = new \Uri\WhatWg\Url('https://miweb.es/blog/post?utm=foo');
$url = $url->withQuery('utm=bar');
echo $url;  // https://miweb.es/blog/post?utm=bar

Importante por dos razones: deja de haber bugs sutiles de codificación (los %20 vs +, las barras en parámetros, los IDN), y se acaba el ciclo eterno de "vale, instalamos league/uri y otra dependencia más".

Lo que NO estamos usando

Callables de primera clase en expresiones constantes

const PROCESADORES = [
    'json' => json_decode(...),
    'xml'  => simplexml_load_string(...),
];

Bonito sobre el papel. En la práctica nuestras tablas de despacho están en clases o configs, no en constantes globales, así que no nos ha cambiado nada. Sospechamos que es una feature pensada para frameworks (Symfony lo aprovechará), no para código de aplicación.

Asymmetric visibility ampliado

Permite declarar public(set: private) y demás combinaciones. Está bien, pero hemos visto que el equipo nuevo tarda más en leer una clase con visibility asimétrica que una con un setter privado. Lo estamos dejando en cuarentena hasta que la legibilidad lo justifique.

Qué romper antes de migrar

Estos son los breaking changes que hemos visto petar en proyectos reales:

CambioSíntoma típicoCómo arreglarlo
MYSQLI_REFRESH_* constantes deprecadasWarning al cargarQuitarlas, ya no se usan
mb_substr_count ahora valida encoding más estrictoExcepción donde antes había warningPasar siempre el argumento $encoding
date() con formato 'P' deprecada usando timezone stringWarning en logsUsar DateTime::format('P') con objeto, no string
var_dump cambia formato de floatsTests con snapshot rotosRegenerar snapshots o usar var_export

Ninguno es grave, pero todos juntos en un proyecto antiguo (Laravel 8, Symfony 5) pueden sumar 50-100 warnings en el primer arranque. Plan típico: un día de purga antes de tocar producción.

Compatibilidad con frameworks

A día de hoy:

  • Laravel 11 y 12: oficialmente soportado desde el día 1.
  • Symfony 7.2 en adelante: oficialmente soportado.
  • WordPress 6.7+: testeado y verde. Plugins clave (WooCommerce, Yoast, ACF) sin problema.
  • Drupal 11: soportado.
  • PrestaShop 8.x: requiere parche en vendor/composer/installers antes de migrar. Lo hemos visto.
  • Magento 2.4.7+: anunciado, pero con caveat de extensiones de terceros. No migrar en producción sin staging exhaustivo.

Cómo migramos nosotros un servidor

Si el cliente ya está en 8.3 u 8.4, el salto a 8.5 es casi un evento de mantenimiento. Pasos:

  1. Staging primero, siempre. La VPS de staging tiene exactamente la misma config que producción.
  2. Smoke test automatizado: scripts que recorren las páginas críticas (login, checkout, dashboard) y comparan respuestas HTTP y tiempos.
  3. Migrar PHP-FPM en horario valle (4-6 AM en proyectos B2C españoles).
  4. Mantener php8.4-fpm instalado en paralelo durante 48 horas. Si algo peta, switch en Nginx en 30 segundos.
  5. Revisar logs de PHP las primeras 24 horas con tail -F y un par de greps preparados (PHP Warning, PHP Deprecated).

Esto no es exclusivo de 8.5. Es el procedimiento que usamos para cualquier salto de versión de PHP. Pero merece la pena recordarlo porque cada vez que un cliente lo hace solo, siempre aparece algún warning que rompe ese plugin de logs antiguo que nadie había tocado en tres años.

¿Migramos tu proyecto?

PHP 8.5 lleva soporte oficial activo hasta diciembre de 2027 y soporte de seguridad hasta diciembre de 2029. Si tu proyecto está en 8.1 o anterior, estás corriendo sobre una versión que ya no recibe parches de seguridad. Esa es la razón principal para actualizar — no el pipe operator.

Si quieres saber qué tendrías que retocar en tu código antes de la migración, hablamos. La auditoría inicial es media jornada; la migración en sí, con staging y rollback preparado, suele cerrarse en uno o dos días.

Referencias

¿Hablamos de tu caso?

Cuéntanos qué necesitas y te respondemos en menos de 24 horas con una propuesta clara.

Pedir presupuesto