El 20 de noviembre de 2025, PHP lanzó oficialmente su versión 8.5, una actualización que trae consigo mejoras significativas orientadas a la productividad del desarrollador y la legibilidad del código. Aunque no introduce cambios radicales de paradigma como lo hizo PHP 8.0, esta versión incluye características que van a transformar la forma en que escribimos código PHP a diario.
En este artículo vamos a explorar en profundidad cada una de las novedades más importantes de PHP 8.5, con ejemplos prácticos que podrás aplicar inmediatamente en tus proyectos Laravel.
El Operador Pipe: Adiós a las Funciones Anidadas
Sin duda, la característica estrella de PHP 8.5 es el operador pipe (|>). Este operador permite encadenar llamadas a funciones de izquierda a derecha, eliminando la necesidad de anidar funciones o crear variables intermedias.
El Problema que Resuelve
Todos hemos escrito código como este en algún momento:
<?php
$title = ' PHP 8.5 Released ';
// Forma tradicional: difícil de leer, de adentro hacia afuera
$slug = strtolower(
str_replace('.', '',
str_replace(' ', '-',
trim($title)
)
)
);
var_dump($slug); // string(15) "php-85-released"Este código funciona, pero leerlo requiere interpretar las funciones desde el interior hacia el exterior, lo cual es poco intuitivo y propenso a errores.
La Solución con el Operador Pipe
Con PHP 8.5, el mismo código se transforma en algo mucho más legible:
<?php
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($str) => str_replace(' ', '-', $str))
|> (fn($str) => str_replace('.', '', $str))
|> strtolower(...);
var_dump($slug); // string(15) "php-85-released"El flujo de datos ahora se lee de arriba hacia abajo, exactamente como lo pensamos lógicamente. Cada paso toma el resultado del anterior y lo transforma.
Reglas del Operador Pipe
El operador pipe funciona bajo estas premisas:
<?php
// El callable a la derecha debe aceptar exactamente un parámetro
$result = 'hello world'
|> strtoupper(...)
|> str_shuffle(...)
|> trim(...)
|> ucfirst(...);
// Equivale a:
$result = ucfirst(trim(str_shuffle(strtoupper('hello world'))));Para funciones que requieren múltiples parámetros, usamos arrow functions:
<?php
$numbers = [1, 2, 3, 4, 5];
$result = $numbers
|> (fn($arr) => array_filter($arr, fn($n) => $n > 2))
|> (fn($arr) => array_map(fn($n) => $n * 2, $arr))
|> array_values(...);
// [6, 8, 10]Caso Práctico en Laravel
Imagina procesar datos de un formulario:
<?php
namespace App\Actions;
class ProcessUserInput
{
public function __invoke(string $input): string
{
return $input
|> trim(...)
|> strip_tags(...)
|> htmlspecialchars(...)
|> (fn($str) => mb_substr($str, 0, 255));
}
}Nueva Extensión URI: Parsing de URLs según Estándares
PHP 8.5 introduce una extensión URI nativa que implementa correctamente tanto el estándar RFC 3986 como el estándar WHATWG URL utilizado por los navegadores. Esto resuelve los problemas históricos de parse_url(), que no seguía ningún estándar específico.
El Problema con parse_url()
La función parse_url() existe desde PHP 4, pero tiene problemas conocidos:
<?php
// parse_url puede dar resultados incorrectos
$result = parse_url('example.com/example/:8080/foo');
var_dump($result);
// array(3) {
// ["host"]=> string(11) "example.com"
// ["port"]=> int(8080)
// ["path"]=> string(18) "/example/:8080/foo"
// }
// ¡El puerto aparece duplicado y el resultado es incorrecto!La Nueva API: Uri\Rfc3986\Uri
Para URIs genéricas siguiendo el estándar RFC 3986:
<?php
use Uri\Rfc3986\Uri;
$uri = new Uri('https://api.example.com:8080/users?id=123&sort=name#profile');
echo $uri->getScheme(); // https
echo $uri->getHost(); // api.example.com
echo $uri->getPort(); // 8080
echo $uri->getPath(); // /users
echo $uri->getQuery(); // id=123&sort=name
echo $uri->getFragment(); // profileModificación Inmutable con with-ers
Los objetos Uri son inmutables. Para modificarlos, usamos métodos with*:
<?php
use Uri\Rfc3986\Uri;
$uri = new Uri('http://example.com/old-path');
$newUri = $uri
->withScheme('https')
->withPath('/new-path')
->withQuery('updated=true');
echo $newUri->toString();
// https://example.com/new-path?updated=true
// El original permanece intacto
echo $uri->toString();
// http://example.com/old-pathUri\WhatWg\Url para Comportamiento de Navegador
Cuando necesites que las URLs se procesen exactamente como lo hace un navegador:
<?php
use Uri\WhatWg\Url;
$url = new Url('https://example.com/path?x=1');
echo $url->getHost();
echo $url->getScheme();Manejo de Errores
<?php
use Uri\Rfc3986\Uri;
use Uri\WhatWg\Url;
// RFC 3986: retorna null si falla el parsing
$uri = Uri::parse('invalid uri');
if ($uri === null) {
// Manejar error
}
// WHATWG: puede capturar errores detallados
$errors = [];
$url = Url::parse(' invalid url', null, $errors);
// $errors contendrá información sobre qué fallóNormalización de URLs
<?php
use Uri\Rfc3986\Uri;
$url = new Uri('HTTPS://thephp.foundation:443/sponsor/');
// Los getters normalizan por defecto
echo $url->toString();
// https://thephp.foundation/sponsor/
// Usa Raw para obtener el valor sin normalizar
echo $url->toRawString();
// HTTPS://thephp.foundation/sponsor/Clone With: Clonación Simplificada para Clases Readonly
Una de las frustraciones al trabajar con clases readonly era la dificultad para clonar objetos modificando algunas propiedades. PHP 8.5 resuelve esto con la nueva sintaxis clone().
El Problema Anterior
<?php
readonly class Color
{
public function __construct(
public int $red,
public int $green,
public int $blue,
public int $alpha = 255,
) {}
// Método tedioso y repetitivo
public function withAlpha(int $alpha): self
{
$values = get_object_vars($this);
$values['alpha'] = $alpha;
return new self(...$values);
}
}La Solución con clone()
<?php
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);Aplicación Práctica con DTOs
<?php
readonly class UserDTO
{
public function __construct(
public string $name,
public string $email,
public ?string $avatar = null,
public bool $isActive = true,
) {}
public function withEmail(string $email): self
{
return clone($this, ['email' => $email]);
}
public function withAvatar(?string $avatar): self
{
return clone($this, ['avatar' => $avatar]);
}
public function deactivate(): self
{
return clone($this, ['isActive' => false]);
}
}
$user = new UserDTO('Isra', '[email protected]');
$updatedUser = $user
->withAvatar('avatar.jpg')
->deactivate();Atributo #[\NoDiscard]: Evita Ignorar Valores de Retorno
El nuevo atributo #[\NoDiscard] permite marcar funciones cuyo valor de retorno no debe ser ignorado. Si el valor no se utiliza, PHP emitirá una advertencia.
Uso Básico
<?php
#[\NoDiscard("El resultado de la validación debe ser verificado")]
function validateEmail(string $email): bool
{
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
// Esto genera un Warning
validateEmail('[email protected]');
// Esto está bien
$isValid = validateEmail('[email protected]');
// Descarte explícito con (void) si realmente quieres ignorarlo
(void) validateEmail('[email protected]');Aplicación en APIs y Servicios
<?php
class PaymentService
{
#[\NoDiscard("El resultado del pago debe ser procesado")]
public function processPayment(float $amount): PaymentResult
{
// ... lógica de pago
return new PaymentResult(/* ... */);
}
}
class ValidationService
{
#[\NoDiscard("Los errores de validación deben ser manejados")]
public function validate(array $data): ValidationResult
{
// ... lógica de validación
return new ValidationResult(/* ... */);
}
}Funciones array_first() y array_last()
Después de años de espera (PHP 7.3 añadió array_key_first() y array_key_last()), finalmente tenemos funciones para obtener directamente el primer y último valor de un array.
Uso Básico
<?php
$users = ['Alice', 'Bob', 'Charlie'];
$first = array_first($users); // 'Alice'
$last = array_last($users); // 'Charlie'
// Con arrays asociativos
$scores = ['alice' => 95, 'bob' => 87, 'charlie' => 92];
$firstScore = array_first($scores); // 95
$lastScore = array_last($scores); // 92Manejo de Arrays Vacíos
<?php
$empty = [];
$first = array_first($empty); // null
$last = array_last($empty); // null
// Perfecto para usar con el operador ??
$firstOrDefault = array_first($items) ?? 'No hay elementos';Comparación con el Código Anterior
<?php
// Antes de PHP 8.5
$first = $array[array_key_first($array)] ?? null;
$last = $array[array_key_last($array)] ?? null;
// O más verboso
$first = reset($array) ?: null;
$last = end($array) ?: null;
// PHP 8.5
$first = array_first($array);
$last = array_last($array);Backtraces en Errores Fatales
Una mejora muy esperada para debugging: los errores fatales ahora incluyen un backtrace completo.
<?php
// Antes de PHP 8.5, un fatal error solo mostraba:
// Fatal error: Maximum execution time exceeded
// Ahora en PHP 8.5:
// Fatal error: Maximum execution time exceeded in /app/heavy-process.php on line 42
// Stack trace:
// #0 /app/heavy-process.php(42): heavyOperation()
// #1 /app/controller.php(15): processData()
// #2 {main}Esta información es invaluable cuando debuggeas problemas en producción.
Closures en Expresiones Constantes
PHP 8.5 permite usar closures estáticos y callables de primera clase en expresiones constantes, como parámetros de atributos:
<?php
#[Attribute]
class Transformer
{
public function __construct(
public Closure $transform,
) {}
}
class User
{
#[Transformer(strtoupper(...))]
public string $name;
#[Transformer(fn($value) => trim($value))]
public string $email;
}Propiedades Final en Promoción de Constructor
Ahora puedes declarar propiedades como final directamente en el constructor:
<?php
class User
{
public function __construct(
final public readonly string $id,
final public string $name,
) {}
}
// Las clases hijas no pueden sobrescribir estas propiedadesVisibilidad Asimétrica en Propiedades Estáticas
Las propiedades estáticas ahora soportan visibilidad asimétrica:
<?php
class Counter
{
public private(set) static int $count = 0;
public static function increment(): void
{
self::$count++;
}
}
// Lectura pública
echo Counter::$count;
// Escritura solo desde dentro de la clase
Counter::increment();Nuevas Funciones get_error_handler() y get_exception_handler()
Para introspección de handlers configurados:
<?php
set_error_handler('myErrorHandler');
$currentHandler = get_error_handler();
// 'myErrorHandler'
if (get_exception_handler() === null) {
set_exception_handler('myExceptionHandler');
}Constante PHP_BUILD_DATE
Nueva constante que expone la fecha de compilación del binario PHP:
<?php
echo PHP_BUILD_DATE;
// "Nov 20 2025 14:30:45"Útil para auditorías de despliegue y verificación de entornos.
Deprecaciones Importantes
PHP 8.5 depreca varias características antiguas:
<?php
// DEPRECADO: casts no canónicos
$bool = (boolean) $value; // Usar (bool)
$int = (integer) $value; // Usar (int)
$float = (double) $value; // Usar (float)
// DEPRECADO: backticks como alias de shell_exec
$output = `ls -la`; // Usar shell_exec('ls -la')
// DEPRECADO: terminar case con punto y coma
switch ($value) {
case 1; // Usar case 1:
break;
}
// SOFT-DEPRECATED: __sleep() y __wakeup()
// Usar __serialize() y __unserialize() en su lugarOPcache Siempre Compilado
A partir de PHP 8.5, OPcache se compila siempre con PHP. Su habilitación sigue controlándose mediante configuración INI (opcache.enable), pero ya no es una extensión opcional.
Conclusión
PHP 8.5 es una versión que prioriza la experiencia del desarrollador. El operador pipe, la extensión URI, y las mejoras en clonación hacen que escribir código limpio y mantenible sea más natural que nunca.
Para proyectos Laravel, estas características se integran perfectamente con el estilo fluido del framework. El operador pipe complementa las colecciones y el pipeline de Laravel, mientras que la extensión URI proporciona una alternativa robusta para el manejo de URLs en tus aplicaciones.
Mi recomendación es que empieces a experimentar con estas características en entornos de desarrollo. Laravel 12 probablemente adoptará PHP 8.5 como versión mínima en el futuro, así que familiarizarte con estas novedades te dará ventaja cuando llegue el momento de actualizar tus proyectos.
Hasta aquí esta entrada revisando las novedades de PHP 8.5, espero que te haya resultado útil, si quieres seguir conociendo todas las novedades de PHP no te pierdas la ruta de PHP 8.