Todos los artículos

PHP 8.5 en apps Laravel: qué se gana un sitio y qué revisar primero

PHP · 4 min de lectura ·

PHP 8.5 salió el 20 de noviembre de 2025, y un anuncio de versión enumera funcionalidades sin decirte cuáles tienen sitio en un código Laravel. Algunas sí cambian cómo se lee el código de la aplicación. Otras resuelven un problema que Laravel resolvió hace años con un helper. Y unas cuantas deprecaciones merecen una búsqueda antes de pasar un proyecto a la nueva versión. Esto es esa criba, funcionalidad por funcionalidad.

Las que se ganan un sitio en el código de la aplicación

array_first() y array_last(). Devuelven el primer o el último valor de un array, o null si está vacío, así que se combinan con ??. Para sacar el último evento había que escribir $events === [] ? null : $events[array_key_last($events)]. Dentro de una colección ya tienes first() y last(), y existe Arr::first(), pero mucho código Laravel maneja arrays simples: valores de config, JSON decodificado, datos de la petición. Ahí es donde estas dos ayudan.

El operador pipe. |> pasa un valor por una cadena de callables, de izquierda a derecha, sin variables temporales. El ejemplo de php.net convierte un título en slug:

$slug = $title
    |> trim(...)
    |> (fn($s) => str_replace(' ', '-', $s))
    |> (fn($s) => str_replace('.', '', $s))
    |> strtolower(...);
// ' PHP 8.5 Released ' becomes 'php-85-released'

Siendo sinceros: en una app Laravel escribirías Str::slug($title), y los strings fluidos y la clase Pipeline ya cubren muchas cadenas de este tipo. El pipe se gana su sitio donde no hay helper, para una secuencia de pequeñas funciones propias que si no anidarías o repartirías en cuatro variables de usar y tirar.

anidado: se escribe de izquierda a derecha, se ejecuta al revés 4. minúsculas 3. quitar puntos 2. espacios a - 1. trim pipe: se escribe y se ejecuta en el mismo orden 1. trim 2. espacios a - 3. quitar puntos 4. minúsculas los mismos cuatro pasos, leídos en el orden en que ocurren
Al anidar tienes que empezar a leer por la llamada más interna. El pipe pone el primer paso en primer lugar.

clone with. Es la que más importa para código que sigue la regla que fijan los propios estándares de código de este sitio: objetos de valor en lugar de arrays cajón de sastre. Una clase readonly es el sitio adecuado para un valor que cruza capas, pero hasta 8.5 cada función «with» obligaba a copiar a mano cada propiedad en una llamada new self(...), o a hacer malabares con get_object_vars(). Ahora clone recibe las propiedades que hay que sustituir:

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

    public function withAlpha(float $alpha): self
    {
        return clone($this, ['alpha' => $alpha]);
    }
}
Color (readonly) alpha = 1.0 el original, intacto antes de 8.5: copiar cada propiedad a new self() 8.5: clone($this, ['alpha' => 0.5]) new Color alpha = 0.5
Los dos caminos acaban en un objeto nuevo y dejan el original en paz. Uno de ellos es una sola línea que no hay que tocar cuando se añade una propiedad.

#[\NoDiscard]. Puesto en una función, hace que PHP avise cuando se ignora el valor de retorno; un cast (void) marca una llamada cuyo resultado se descarta a propósito. Encaja muy bien con los objetos resultado: una función que devuelve éxito o fallo en lugar de lanzar una excepción es justo la que alguien llama y se olvida de comprobar.

La extensión URI. Viene incluida, siempre está disponible y analiza URL según la RFC 3986 o el estándar URL de WHATWG, apoyándose en uriparser y Lexbor. (new Uri\Rfc3986\Uri($url))->getHost() sustituye a parse_url($url)['host'], y tienes un parser que sigue un estándar con nombre en lugar de las reglas propias de parse_url(). Allí donde compruebes un host antes de redirigir, o compares la URL de un webhook con una lista permitida, el cambio compensa.

El resto de la lista, los handles compartidos persistentes de cURL, las closures en expresiones constantes, los backtraces en errores fatales como un timeout, los atributos en constantes, es útil pero es menos probable que cambie el código diario de controladores y servicios.

Qué revisar antes de actualizar

Nada de esto es motivo para esperar; es una lista de cosas que buscar. Usar null como clave de un array, o en array_key_exists(), está deprecado, y en código Laravel eso suele esconderse en una búsqueda cuya clave puede ser nula. Los casts no canónicos (integer), (boolean), (double) y (binary) están deprecados, igual que el operador de comillas invertidas y un case que termina en punto y coma. __sleep() y __wakeup() quedan deprecados de forma suave en favor de __serialize() y __unserialize(): revisa tus propias clases que se serializan en payloads de colas o en la caché. PHP ahora avisa cuando un float que no se puede representar se convierte a int, cuando se convierte NAN y cuando desestructuras algo que no es ni un array ni null. En el servidor, el ajuste ini disable_classes ha desaparecido.

Este sitio es un buen ejemplo de dónde están la mayoría de los proyectos. Funciona con Laravel 13.35.0, y su composer.json exige PHP ^8.4.1. PHP 8.5 cumple esa restricción, pero 8.4 también, así que ninguna de las sintaxis de 8.5 de arriba puede entrar en este código hasta que se suba la restricción.

Primero actualiza el runtime y después la sintaxis: ejecuta la batería de tests en 8.5 con las deprecaciones visibles, corrige lo que aparezca y solo entonces sube la versión de PHP en composer.json para que el código nuevo pueda usar el pipe y clone with.

Un primer paso concreto para un proyecto Laravel: busca en el código __sleep, __wakeup, (integer), (boolean) y las comillas invertidas, y luego ejecuta los tests en 8.5. Después, la primera clase readonly que toques es un buen sitio para probar clone with. Fuente: php.net, anuncio de la versión PHP 8.5.

Notas de los lectores

  1. Todavía no hay notas. La primera siempre es la más valiente.
¿Estás construyendo algo parecido? Puedo ayudarte. Escríbeme por WhatsApp