PHP 8.5: Las 7 Novedades que Debes Conocer [Guía 2025]

PHP 8.5 salió el 20 de noviembre de 2025 y trae un buen puñado de novedades. Estas son las que más me han llamado la atención.

Novedades destacadas de PHP 8.5

Operador Pipe |>

La novedad estrella de PHP 8.5 para mí. El operador pipe encadena funciones de forma mucho más legible. Se acabó leer las funciones anidadas de dentro hacia fuera: ahora el código se lee en el orden en el que se ejecuta.

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

Nueva sintaxis para clonar un objeto y modificar propiedades en la misma línea. Si trabajas con clases readonly e inmutables (y deberías, en muchos casos), esto te va a quitar bastantes métodos helper.

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 hay funciones nativas para obtener el primer y último elemento de un array sin tener que tirar de combinaciones raras.

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] 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

Hasta ahora, cuando ocurría un error fatal en PHP, no había información del stack trace, lo que dificultaba mucho la depuración. PHP 8.5 lo arregla.

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. Permite tener 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. El operador pipe es la que más me ha gustado: hará que el código sea mucho más legible. Las mejoras en clonación y las nuevas funciones de arrays también pintan muy bien. ¿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.

Luis Miguel Martín
Luis Miguel Martín

CTO en LCApps. Escribo sobre Claude Code, MCP, agentes IA y arquitectura de software real.

💡

¿Te ha gustado este artículo?

Explora más artículos sobre desarrollo, buenas prácticas y herramientas.