PHP 8.5: Descubre las novedades

Introducción a PHP 8.5

La última versión de PHP, la 8.5, ha sido lanzada el 20 de noviembre de 2025, y trae consigo una serie de características y mejoras significativas. En este artículo, exploraremos en detalle las novedades más relevantes de PHP 8.5.

Novedades destacadas de PHP 8.5

Operador Pipe |>

Una de las novedades más destacadas de PHP 8.5 es el operador pipe, que nos permite encadenar funciones de forma más legible. En lugar de tener funciones anidadas difíciles de leer, ahora podemos escribir código que se lee de izquierda a derecha.

Antes:

// Procesar un texto para crear un slug
$texto = "  Mi Artículo de PHP 8.5  ";
$slug = strtolower(str_replace(' ', '-', trim($texto)));
// Resultado: "mi-artículo-de-php-8.5"

Ahora:

// El operador pipe pasa el resultado de la izquierda como argumento a la función de la derecha
$texto = "  Mi Artículo de PHP 8.5  ";
$slug = $texto
    |> trim(...)
    |> fn($s) => str_replace(' ', '-', $s)
    |> strtolower(...);
// Resultado: "mi-artículo-de-php-8.5"
// Mucho más legible: se lee de arriba a abajo

Clone With

PHP 8.5 introduce una sintaxis mejorada para clonar objetos y modificar sus propiedades al mismo tiempo. Esto es especialmente útil para trabajar con clases readonly e inmutables.

Antes:

readonly class Usuario {
    public function __construct(
        public string $nombre,
        public string $email,
        public bool $activo = true,
    ) {}

    public function activar(): self {
        $clone = clone $this;
        $clone->activo = true; // Error: no se puede modificar propiedad readonly
        return $clone;
    }
}

Ahora:

readonly class Usuario {
    public function __construct(
        public string $nombre,
        public string $email,
        public bool $activo = true,
    ) {}

    // Podemos clonar y modificar propiedades en una sola línea
    public function activar(): self {
        return clone($this, ['activo' => true]);
    }

    public function cambiarEmail(string $nuevoEmail): self {
        return clone($this, ['email' => $nuevoEmail]);
    }
}

$usuario = new Usuario('Luis', 'luis@example.com', false);
$usuarioActivo = $usuario->activar();
// El objeto original no se modifica, se crea uno nuevo con la propiedad actualizada

Importante: clone with solo funciona desde dentro de la clase (en métodos de la propia clase). Si intentas clonar y modificar propiedades readonly desde fuera, dará error.

Funciones array_first() y array_last()

Por fin tenemos funciones nativas para obtener el primer y último elemento de un array sin tener que usar combinaciones raras de funciones.

Antes:

$numeros = [10, 20, 30, 40, 50];

// Para obtener el primer elemento
$primero = reset($numeros); // Modifica el puntero interno del array
// o
$primero = array_values($numeros)[0] ?? null; // Poco eficiente

// Para obtener el último elemento
$ultimo = end($numeros); // También modifica el puntero interno
// o
$ultimo = array_slice($numeros, -1)[0] ?? null; // Poco intuitivo

Ahora:

$numeros = [10, 20, 30, 40, 50];

// Obtener el primer elemento de forma clara y sencilla
$primero = array_first($numeros); // 10

// Obtener el último elemento
$ultimo = array_last($numeros); // 50

// Si el array está vacío, devuelve null
$vacio = [];
$resultado = array_first($vacio); // null

// Funciona con arrays asociativos también
$usuario = [
    'id' => 1,
    'nombre' => 'Luis',
    'email' => 'luis@example.com'
];
$primerValor = array_first($usuario); // 1
$ultimoValor = array_last($usuario); // 'luis@example.com'

Atributo #[NoDiscard]

El nuevo atributo #[NoDiscard] nos ayuda a evitar errores al indicar que el valor de retorno de una función es importante y no debe ignorarse.

Ejemplo:

class GestorArchivos {
    // Marcamos que el valor de retorno es importante
    #[\NoDiscard("El estado de la operación es crítico para evitar pérdida de datos")]
    public function guardarArchivo(string $ruta, string $contenido): bool {
        return file_put_contents($ruta, $contenido) !== false;
    }
}

$gestor = new GestorArchivos();

// Esto genera una advertencia (Warning)
$gestor->guardarArchivo('/tmp/test.txt', 'contenido');
// Warning: Return value of guardarArchivo() should not be discarded

// La forma correcta es usar el valor de retorno
if ($gestor->guardarArchivo('/tmp/test.txt', 'contenido')) {
    echo "Archivo guardado correctamente";
} else {
    echo "Error al guardar el archivo";
}

// Si realmente queremos ignorar el valor, podemos usar (void)
(void) $gestor->guardarArchivo('/tmp/test.txt', 'contenido');

Stack traces en errores fatales

Anteriormente, cuando ocurría un error fatal en PHP, no teníamos información del stack trace, lo que dificultaba mucho la depuración. PHP 8.5 soluciona este problema.

Antes:

function dividir($a, $b) {
    return $a / $b;
}

function calcular() {
    return dividir(10, 0); // División por cero
}

calcular();

// En versiones anteriores obtenías:
// Fatal error: Uncaught DivisionByZeroError in file.php:3
// Sin información de qué función llamó a dividir()

Ahora:

function dividir($a, $b) {
    return $a / $b;
}

function calcular() {
    return dividir(10, 0);
}

calcular();

// En PHP 8.5 obtienes el stack trace completo:
// Fatal error: Uncaught DivisionByZeroError in file.php:3
// Stack trace:
// #0 file.php(7): dividir(10, 0)
// #1 file.php(10): calcular()
// Ahora sabes exactamente desde dónde se llamó la función que causó el error

Closures en constantes y atributos

PHP 8.5 permite usar closures en expresiones constantes y atributos, lo que abre nuevas posibilidades para la programación funcional.

Ejemplo:

// Closures en constantes
class Calculadora {
    public const SUMAR = fn($a, $b) => $a + $b;
    public const MULTIPLICAR = fn($a, $b) => $a * $b;
}

$resultado = (Calculadora::SUMAR)(5, 3); // 8
$producto = (Calculadora::MULTIPLICAR)(4, 2); // 8

// Closures en atributos
#[\Attribute]
class Validador {
    public function __construct(
        public \Closure $validacion
    ) {}
}

class Usuario {
    #[Validador(fn($valor) => strlen($valor) >= 3)]
    public string $nombre;

    #[Validador(fn($valor) => filter_var($valor, FILTER_VALIDATE_EMAIL))]
    public string $email;
}

// Ahora podemos crear validadores más flexibles usando closures directamente

Visibilidad asimétrica en propiedades estáticas

PHP 8.5 extiende la visibilidad asimétrica (introducida en PHP 8.4 para propiedades de instancia) a las propiedades estáticas. Esto permite tener una visibilidad pública para lectura pero privada/protegida para escritura.

Ejemplo:

class Contador {
    // Lectura pública, escritura privada
    public private(set) static int $total = 0;

    public static function incrementar(): void {
        self::$total++; // Permitido: estamos dentro de la clase
    }

    public static function getTotal(): int {
        return self::$total; // Lectura permitida desde cualquier lugar
    }
}

// Lectura permitida
echo Contador::$total; // 0

// Escritura desde fuera genera error
Contador::$total = 100; // Error: Cannot modify private(set) property

// Solo se puede modificar a través de métodos de la clase
Contador::incrementar();
echo Contador::$total; // 1

Deprecaciones importantes

PHP 8.5 también depreca algunas características que deberías evitar en código nuevo:

Casts no estándar

// Estos casts quedan deprecados:
$valor = (boolean) $var;  // Usar (bool) en su lugar
$valor = (integer) $var;  // Usar (int) en su lugar
$valor = (real) $var;     // Usar (float) en su lugar

Backticks para ejecutar comandos

// Deprecado: usar backticks para ejecutar comandos del sistema
$output = `ls -la`;

// Recomendado: usar funciones específicas
$output = shell_exec('ls -la');
// o mejor aún
$output = exec('ls -la');

Redeclaración de constantes

// Deprecado: redeclarar constantes con define()
define('MI_CONSTANTE', 'valor1');
define('MI_CONSTANTE', 'valor2'); // Genera advertencia de deprecación

// Esto se prohibirá completamente en PHP 9.0

Conclusión

Estas son las novedades que más me han llamado la atención de la nueva versión de PHP. El operador pipe es sin duda una de las mejoras que más me ha gustado y hará que nuestro código sea mucho más legible. Las mejoras en clonación y las nuevas funciones de arrays también son muy interesantes. ¿Y a ti? ¿Qué te parecen? ¿Vas a actualizar a la nueva versión?

Puedes consultar más información en la web oficial y en stitcher.io.

PD: Si tenéis cualquier duda podéis poneros en contacto conmigo enviando un DM por twitter.