PHP 8.5: Todas las novedades con ejemplos prácticos

El 20 de noviembre de 2025 se lanzó PHP 8.5, la última actualización importante del lenguaje que sustenta gran parte de la web moderna. Como desarrollador fullstack especializado en Laravel, Vue.js e Inertia.js, sigo de cerca cada evolución del ecosistema PHP. Esta versión trae consigo novedades que inciden de forma concreta en la calidad del código que escribimos a diario: un nuevo operador pipe, una extensión URI nativa, soporte para clonación con modificación de propiedades y mucho más. Veámoslas juntas, con ejemplos prácticos.

1. Operador Pipe |> — Adiós a las funciones anidadas

Es probablemente la novedad más esperada por la comunidad. El operador pipe (|>) permite encadenar la salida de una función como entrada de la siguiente, leyendo el código de izquierda a derecha en lugar de dentro hacia fuera.

Antes (PHP 8.4 y anteriores):

$title = ' PHP 8.5 Released ';

$slug = strtolower(
    str_replace('.', '',
        str_replace(' ', '-',
            trim($title)
        )
    )
);

// Output: "php-85-released"

Después (PHP 8.5):

$title = ' PHP 8.5 Released ';

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

// Output: "php-85-released"

El operador evalúa de izquierda a derecha: el lado derecho acepta un callable con un solo parámetro y recibe el valor del lado izquierdo. Para funciones nativas que aceptan un único argumento se usa la sintaxis trim(...); para funciones con más argumentos se envuelve en una arrow function entre paréntesis.

¿Por qué es relevante en Laravel? Pensad en cuántas veces manipuláis cadenas, arrays o transformaciones de datos en los controllers o services, pasando el resultado de una función a otra. Con el operador pipe el código se vuelve lineal y declarativo, perfectamente alineado con la filosofía de Laravel.

2. Nueva extensión URI — Análisis moderno de URLs

Durante años nos hemos apoyado en parse_url(), una función conocida por sus limitaciones con entradas mal formadas y por no cumplir con los estándares. PHP 8.5 introduce una extensión URI nativa y siempre disponible, basada en las bibliotecas uriparser (RFC 3986) y Lexbor (WHATWG URL).

Antes (PHP 8.4):

$components = parse_url('https://ejemplo.es/blog/php-85');

echo $components['host'];   // "ejemplo.es"
echo $components['path'];   // "/blog/php-85"
// Sin validación, sin normalización

Después (PHP 8.5 — RFC 3986):

use Uri\Rfc3986\Uri;

$uri = new Uri('https://ejemplo.es/blog/php-85');

echo $uri->getScheme();  // "https"
echo $uri->getHost();    // "ejemplo.es"
echo $uri->getPath();    // "/blog/php-85"

// Objetos inmutables con patrón wither
$newUri = $uri->withPath('/blog/laravel-12');
echo $newUri->toString();
// "https://ejemplo.es/blog/laravel-12"

Con el estándar WHATWG URL:

use Uri\WhatWg\Url;

$url = new Url('https://ejemplo.es/blog/php-85');

echo $url->getScheme();       // "https"
echo $url->getUnicodeHost();  // "ejemplo.es"
echo $url->getAsciiHost();    // "ejemplo.es"

Ambas clases son readonly, admiten el análisis mediante constructor o método factory parse(), y lanzan excepciones ante entradas no válidas. La extensión RFC 3986 también soporta la representación «raw» y «normalized-decoded», útil cuando se trabaja con URLs codificadas en porcentaje. Esta novedad resulta especialmente interesante para quienes desarrollan API RESTful con Laravel, donde la manipulación correcta de URIs es fundamental.

3. Clone With — Clonación con modificación de propiedades

Con la llegada de las propiedades readonly en PHP 8.1, clonar objetos se volvió engorroso: no era posible modificar directamente una propiedad readonly en el clon. PHP 8.5 resuelve el problema transformando clone en una función que acepta un array asociativo de propiedades a sobrescribir.

Antes (PHP 8.4):

readonly class Color
{
    public function __construct(
        public int $red,
        public int $green,
        public int $blue,
        public int $alpha = 255,
    ) {}

    public function withAlpha(int $alpha): self
    {
        // Workaround necesario
        $values = get_object_vars($this);
        $values['alpha'] = $alpha;
        return new self(...$values);
    }
}

Después (PHP 8.5):

readonly class Color
{
    public function __construct(
        public int $red,
        public int $green,
        public int $blue,
        public int $alpha = 255,
    ) {}

    public function withAlpha(int $alpha): self
    {
        return clone($this, ['alpha' => $alpha]);
    }
}

$blue = new Color(79, 91, 147);
$transparentBlue = $blue->withAlpha(128);

echo $transparentBlue->alpha; // 128

Si trabajáis con DTO (Data Transfer Objects) o Value Objects en vuestros proyectos Laravel, esta funcionalidad os simplificará enormemente la vida. Pensad en los DTO que usáis con Inertia.js para pasar datos estructurados a los componentes Vue: ahora el patrón «wither» es nativo y limpio.

4. Atributo #[\NoDiscard] — No ignores los valores de retorno

¿Cuántas veces habéis llamado a una función olvidando usar el valor devuelto? Con #[\NoDiscard], PHP 8.5 emitirá un warning cuando el valor de retorno se ignore.

#[\NoDiscard("El resultado contiene posibles errores")]
function processOrders(array $orders): array
{
    $results = [];
    foreach ($orders as $order) {
        $results[] = $order->process();
    }
    return $results;
}

// ⚠️ Warning: el valor de retorno no se utiliza
processOrders($pendingOrders);

// ✅ Correcto
$results = processOrders($pendingOrders);

// ✅ Ignorar intencionadamente con cast (void)
(void) processOrders($pendingOrders);

El cast (void) es una novedad de PHP 8.5 que permite indicar explícitamente que estáis ignorando el resultado. Si desarrolláis paquetes Laravel o bibliotecas compartidas, añadir #[\NoDiscard] a los métodos críticos es una forma excelente de hacer vuestras APIs más seguras.

5. Closures en expresiones constantes — Más potencia para los atributos

Las closures estáticas y los first-class callables ya pueden usarse en expresiones constantes. Esto abre escenarios muy interesantes, especialmente con los atributos PHP.

// Antes: se necesitaba un objeto Expression
#[AccessControl(
    new Expression('request.user === post.getAuthor()')
)]
public function update(Request $request, Post $post): Response
{
    // ...
}

// Después (PHP 8.5): closure directamente en el atributo
#[AccessControl(static function (
    Request $request,
    Post $post,
): bool {
    return $request->user === $post->getAuthor();
})]
public function update(Request $request, Post $post): Response
{
    // ...
}

En un contexto Laravel, pensad en la posibilidad de definir lógicas de autorización, validación o configuración directamente en los atributos de los controllers, sin necesidad de crear clases dedicadas para cada regla sencilla.

6. array_first() y array_last() — ¡Por fin!

Dos funciones solicitadas por la comunidad durante años. Devuelven el primer o último valor de un array, o null si el array está vacío.

// Antes (PHP 8.4)
$lastEvent = $events === []
    ? null
    : $events[array_key_last($events)];

// Después (PHP 8.5)
$firstEvent = array_first($events);
$lastEvent = array_last($events);

// Funciona con arrays asociativos
$config = ['db' => 'mysql', 'cache' => 'redis', 'queue' => 'sqs'];
echo array_first($config); // "mysql"
echo array_last($config);  // "sqs"

// Devuelve null para arrays vacíos
$empty = [];
var_dump(array_first($empty)); // null — perfecto con ??
$fallback = array_first($empty) ?? 'default';

Sí, Laravel cuenta con Arr::first() y las colecciones tienen ->first(), pero disponer de estas funciones de forma nativa en el lenguaje supone una ventaja en rendimiento y legibilidad, sobre todo cuando trabajáis fuera del contexto de Eloquent.

7. Handles compartidos persistentes de cURL

Los nuevos handles compartidos persistentes evitan el coste de reinicialización de las conexiones cURL entre peticiones PHP. Esto resulta especialmente útil para aplicaciones con alto rendimiento.

// PHP 8.5: handle compartido persistente
$sh = curl_share_init_persistent([
    CURL_LOCK_DATA_DNS,
    CURL_LOCK_DATA_CONNECT,
]);

$ch = curl_init('https://api.ejemplo.es/v1/orders');
curl_setopt($ch, CURLOPT_SHARE, $sh);

// Puede reutilizar la conexión de una petición SAPI anterior
curl_exec($ch);

Si vuestra aplicación Laravel realiza muchas llamadas HTTP externas (APIs de terceros, microservicios, webhooks), esta optimización puede reducir sensiblemente la latencia global.

8. Nueva directiva INI max_memory_limit — Un techo para la memoria

Quienes desarrollamos aplicaciones PHP sabemos que es posible sobrescribir el memory_limit en tiempo de ejecución con ini_set(), llegando incluso a eliminar cualquier límite con el valor -1. Esto puede resultar peligroso, especialmente en entornos de hosting compartido o cuando no se tiene plena visibilidad sobre los recursos del servidor.

PHP 8.5 introduce la directiva max_memory_limit en el fichero php.ini, que actúa como un techo máximo no sobrescribible desde el código de la aplicación. Aunque un script intente elevar el límite, el valor nunca podrá superar el definido en esta nueva directiva.

Configuración en php.ini:

// php.ini
max_memory_limit = 256M
memory_limit = 128M

Comportamiento en el código PHP:

ini_set('memory_limit', '256M');  // ✅ OK, dentro del límite
ini_set('memory_limit', '512M');  // ⚠️ Warning, se establece en 256M
ini_set('memory_limit', '-1');    // ⚠️ Warning, se establece en 256M

// Buena práctica: verificar el límite antes de actuar
$maxLimit = ini_get('max_memory_limit');
if ($maxLimit !== false) {
    // Adaptar el comportamiento al techo disponible
}

Para quienes trabajamos con Laravel, esta novedad resulta especialmente relevante al gestionar jobs pesados en las colas (importación masiva de CSV, procesamiento de imágenes, generación de PDF) o al desplegar en plataformas cloud donde la memoria es un recurso de consumo. Saber que existe un techo infranqueable hace más predecible el comportamiento de la aplicación en producción y previene caídas inesperadas.

9. Otras novedades destacables

PHP 8.5 trae consigo también una serie de mejoras más contenidas pero igualmente relevantes para el desarrollo diario:

Backtrace en errores fatales: los errores fatales (como la superación del tiempo máximo de ejecución) ahora incluyen un backtrace completo, haciendo la depuración mucho más rápida.

// Ahora veréis algo como:
// Fatal error: Maximum execution time exceeded in example.php on line 6
// Stack trace:
// #0 example.php(6): heavyComputation()
// #1 example.php(10): processData()
// #2 {main}

Propiedades estáticas con visibilidad asimétrica: ahora podéis tener una propiedad estática que sea legible públicamente pero solo modificable de forma privada.

Propiedades final con constructor promotion: útil para garantizar que una propiedad no sea sobrescrita en las clases hijas.

Closure::getCurrent(): un nuevo método que simplifica la recursión en funciones anónimas.

Cookie partitioned: setcookie() y setrawcookie() soportan ahora la clave «partitioned» (CHIPS), importante para la gestión de la privacidad en los navegadores modernos.

OPcache siempre compilado: OPcache ya no es opcional y ahora se compila estáticamente en PHP, como ext/date o ext/pcre. La activación sigue controlada vía INI.

Constante PHP_BUILD_DATE: una nueva constante que devuelve la fecha de compilación del build PHP en uso, útil para la depuración en producción.

echo PHP_BUILD_DATE; // "Nov 20 2025 10:30:45"

10. Deprecaciones a tener en cuenta

Como en cada versión, PHP 8.5 introduce varias deprecaciones que es importante conocer antes de actualizar vuestros proyectos:

Operador backtick deprecado: el uso de los backticks como alias de shell_exec() está ahora deprecado. Usad explícitamente shell_exec().

Casts no canónicos deprecados: (boolean), (integer), (double) y (binary) están deprecados. Usad (bool), (int), (float) y (string).

__sleep() y __wakeup() soft-deprecated: la recomendación es migrar a __serialize() y __unserialize().

Case con punto y coma: terminar una sentencia case con ; en lugar de : está ahora deprecado.

null como offset de array: usar null como clave de array o con array_key_exists() está deprecado. Usad una cadena vacía.

Eliminación de disable_classes: la configuración INI disable_classes ha sido eliminada.

11. PHP 8.5 y Laravel: compatibilidad y consejos

Laravel 12, lanzado en febrero de 2025, soporta oficialmente PHP 8.2–8.4. Como es habitual, el soporte para la nueva versión GA se añade poco después del lanzamiento, una vez que las dependencias del framework resultan compatibles. Si estáis valorando la actualización, este es mi consejo práctico:

Si todavía no estáis en PHP 8.4, comenzad por ahí: es el trampolín más seguro, dado que Laravel 12 lo soporta plenamente. Esto os permite identificar y actualizar paquetes y extensiones desactualizados, evitando afrontar demasiados cambios de golpe. Una vez estabilizados en 8.4, probad vuestra aplicación en 8.5 en un entorno de staging. La mayoría de las novedades de PHP 8.5 son aditivas, pero las deprecaciones y los nuevos warnings pueden sacar a la luz problemas en código legacy o en paquetes de terceros aún no actualizados.

Centrad las pruebas en las funcionalidades críticas para vuestros usuarios: tiempos de respuesta en las páginas principales, flujos de autenticación, colas de jobs y — si utilizáis Inertia.js — verificad que el paso de datos entre Laravel y Vue funcione correctamente bajo carga.

PHP 8.5 no es un lanzamiento revolucionario, pero sí una versión madura y pragmática. El operador pipe mejora la legibilidad, la extensión URI moderniza un área crítica del lenguaje, clone with simplifica los patrones inmutables y las pequeñas adiciones como array_first()/array_last() eliminan código repetitivo del día a día. Para quienes trabajamos con el stack Laravel + Vue + Inertia.js, estas novedades se traducen en código más limpio, APIs más seguras y una experiencia de desarrollo globalmente mejor.

Si necesitáis ayuda para actualizar vuestros proyectos a PHP 8.5, evaluar la compatibilidad de vuestro stack o desarrollar nuevas aplicaciones aprovechando al máximo estas novedades, contactad conmigo: estaré encantado de ayudaros.


Fuentes: php.net — PHP 8.5 Release Announcement, PHP RFC Wiki