Drupal

Sí, pero solo desde Drupal 11.3. Antes de subir PHP 8.5 en el hosting, actualiza el core y ajusta config.platform en composer.json.

El orden importa más que la versión: si declaras PHP 8.5 en Composer mientras el core sigue en 11.1 o 11.2, el sitio queda sin soporte de PHP y Drupal deja de arrancar con consistencia. Esta es la ruta que usamos en proyectos reales y los puntos donde más se traba la actualización.

Drupal 11.3 es la puerta de entrada a PHP 8.5

Drupal 11.3 es la primera versión de Drupal que corre sobre PHP 8.5. Ni 11.1 ni 11.2 lo soportan, así que la actualización de PHP en tu hosting depende de subir primero el core a 11.3 o posterior. A partir de ahí sí puedes aprovechar el pipe operator, array_first(), array_last() y clone con propiedades en código real de Drupal.

El soporte se aprobó en el meta issue #3523596 de drupal.org y aterrizó en el core de 11.3. PHP 8.5.0 se publicó el 20 de noviembre de 2025, según el anuncio oficial de php.net. Si vas a quedarte en una versión concreta de Drupal, la 11.4.5 (publicada el 6 de agosto de 2026) mantiene cobertura de seguridad hasta junio de 2027, como se detalla en las notas de la release.

Qué trae PHP 8.5

Los cambios que le interesan a un proyecto Drupal son pocos, pero útiles:

  • Operador pipe |>, que completa la familia junto con la arrow function ?->. Enlaza el valor de la izquierda como primer argumento de la llamada de la derecha.
  • array_first() y array_last(), que evitan el clásico reset($array) / end($array) con sus efectos colaterales sobre el puntero interno.
  • clone con propiedades, que permite clone $objeto with ['propiedad' => valor].
  • Atributo #[\NoDiscard], para marcar funciones cuyo valor de retorno no debería ignorarse.
  • Extensión URI y handles persistentes de cURL compartidos, útiles en integraciones que hablan mucho con APIs externas.

La tabla de compatibilidad, leída bien

La tabla oficial de requisitos de PHP es la referencia, pero hay que leerla con cuidado porque se actualiza por fila, no por columna:

  • PHP 8.5 funciona en Drupal 11.3, 11.4 y 12.0.
  • PHP 8.5 no funciona en Drupal desde 10.4 hasta 11.2. Si tu sitio está en 11.1 o 11.2 y cambias la versión de PHP en el hosting sin tocar el core, te quedas sin soporte.
  • PHP 8.4 cubre 11.1 hasta 11.4, que es la opción de contexto si todavía no quieres saltar a 11.3.

La ruta segura es core primero, PHP después:

# 1. Sube el core manteniendo la versión actual de PHP
composer update drupal/core --with-all-dependencies
# 2. Confirma que ya estás en 11.3 o superior
composer show drupal/core
# 3. Solo entonces ajusta la plataforma de PHP

Declarar la versión de PHP en composer.json

El paso que más se salta la gente es config.platform. Si tu composer.json dice que el proyecto corre en PHP 8.3, Composer resuelve dependencias como si fuera 8.3, y los paquetes que exigen 8.4 o más simplemente no entran, aunque el servidor ya tenga la versión nueva.

{
"require": {
"php": ">=8.3",
"drupal/core-composer-scaffold": "^11"
},
"config": {
"platform": {
"php": "8.5.0"
},
"allow-plugins": {
"drupal/core-composer-scaffold": true,
"composer/installers": true,
"php-http/discovery": true
}
}
}

config.platform fija la versión que Composer asume al resolver, no la que el servidor ejecuta. Por eso los dos lados tienen que coincidir: si declaras 8.5 y el hosting sigue en 8.3, el sitio funciona en local y revienta en producción.

El orden también importa aquí. Cambia config.platform antes de correr el update de dependencias, nunca después. Si lo cambias después, el composer.lock sigue conteniendo paquetes resueltos con la plataforma vieja y queda inconsistente.

# Verifica que el lock y la plataforma declarada coinciden
composer check-platform-reqs
composer validate

Si composer.lock está versionado, y debería estarlo en un proyecto Drupal, ese cambio tiene que ir en el mismo commit que el ajuste de platform. Si no, el siguiente composer install en CI reproduce el árbol viejo.

El pipe operator en código de Drupal

|> se lee bien cuando encadenas transformaciones sobre un valor que ya existe, especialmente en código procedural, que sigue siendo abundante en hooks de preprocess y en lógica de servicios.

// Antes
$markup = '<div class="node">' . $node->label() . '</div>';
$markup = trim(strip_tags($markup));
$markup = htmlspecialchars($markup, ENT_QUOTES, 'UTF-8');
$build['#prefix'] = $markup;
// Con pipe
$build['#prefix'] = '<div class="node">' . $node->label() . '</div>'
|> strtr(...)
|> strip_tags(...)
|> trim(...)
|> htmlspecialchars(...);

Donde el pipe shine de verdad es en composición y filtrado de arrays, que es patrón constante en código contributed:

$build = array_filter(
$build + ['#type' => 'details', '#open' => TRUE],
fn ($value) => $value !== NULL && $value !== '',
);

Ojo con la legibilidad: si metes cinco pipes, el código deja de ser legible. El pipe sirve para tres o cuatro transformaciones, no más. Y remember que el valor de la izquierda entra como primer argumento, así que la firma de la función de la derecha decide si el pipe aplica.

Lecturas de arrays y clones sin efectos colaterales

array_first() y array_last() en preprocess

array_first() y array_last() no mueven el puntero interno del array, que era el problema real de reset() y end(). En un hook donde el mismo array se recorre varias veces, esa diferencia evita resultados intermitentes que son molestos de depurar.

function mi_modulo_preprocess_node(array &$variables): void {
$field = $variables['node']->getFieldItems('field_items');
if ($field->isEmpty()) {
return;
}
$values = array_column($field->getValue(), 'value');
// Antes: movía el puntero interno del array
$primero = reset($values);
$ultimo = end($values);
// PHP 8.5
$primero = array_first($values);
$ultimo = array_last($values);
}

El cambio semántico es sutil pero importa: las cuatro devuelven false cuando el array está vacío. La diferencia real es que array_first() y array_last() no alteran el puntero interno, así que el siguiente foreach o current() se comporta como esperas. En Drupal, donde los arrays se comparten entre varias capas del render array, eso evita fallos que solo aparecen bajo ciertas condiciones de orden.

En servicios, el caso típico es tomar el primer elemento de una colección inyectada:

final class ResaltadorService {
public function __construct(
private readonly array $colores,
) {}
public function predeterminado(): string {
return array_first($this->colores) ?? '#ffffff';
}
}

clone with en servicios y pruebas

La sintaxis clone $obj with [...] es útil con las clases readonly de PHP 8.2 en adelante, que solemos usar en servicios Drupal. Antes, clonar un objeto con propiedades readonly era penoso: tocaba reflection o un constructor con parámetros opcionales que a veces chocaba con la inmutabilidad.

$clon = clone $servicio with ['debug' => TRUE, 'logger' => $loggerMock];

El punto fuerte está en pruebas. Antes necesitabas un constructor con parámetros opcionales o un setter que a veces peleaba con readonly; ahora el clon se configura de una vez:

$subject = new Subject();
$subjectTest = clone $subject with [
'logger' => $loggerMock,
'transport' => 'array',
];

También sirve para variantes de una misma instancia sin duplicar la lógica del constructor, siempre que la clase sea inmutable o al menos no tenga estado que se comparta entre clones.

Deprecaciones que conviene limpiar antes de subir

Las funciones nuevas son según el gusto. Lo que de verdad genera trabajo en sitios con años de código son las deprecaciones. En PHP 8.5 quedaron obsoletas, y la lista completa está en php.net:

  • null como índice de array. Código como $array[null], o accesos donde un parámetro llega nulo. Antes era un aviso silencioso; ahora ensucia el log y el comportamiento puede cambiar.
  • Casts no canónicos. Los casts simples siguen bien, pero formas no canónicas y casts de objetos a tipos incompatibles avisan.
  • __sleep() y __wakeup(), en favor de __serialize() / __unserialize(). En Drupal esto pega sobre todo en caché y en cualquier objeto que viaje por serialize().

Antes de subir, busca en tu código y en el contributed:

grep -rn '__sleep\|__wakeup' modules contrib themes
grep -rn 'reset(\|end(' modules contrib themes

Y revisa el log de errores de PHP después del despliegue, no solo el estado de la página. Muchas deprecaciones no rompen la funcionalidad pero delatan código que se va a romper en PHP 9.

Orden de actualización recomendado

  1. Confirma la versión de tu core y actualízala a 11.3 o superior usando la versión de PHP que ya tienes.
  2. Revisa el changelog de los módulos contributed que usas y confirma que soportan la versión de PHP destino.
  3. Cambia config.platform en composer.json y corre composer update en el mismo commit.
  4. Ejecuta composer check-platform-reqs y la suite de pruebas.
  5. Cambia la versión de PHP en el hosting y en el Dockerfile o la configuración del entorno.
  6. Revisa el log de errores en las primeras 24 horas, en busca de deprecaciones de 8.5.

Si algo falla en el paso 3, casi siempre es un módulo contributed que fija una restricción de versión de PHP. La salida es buscar una versión más reciente del módulo o un fork, no forzar con --ignore-platform-reqs: eso esconde el problema hasta producción.

Preguntas frecuentes

¿Puedo usar PHP 8.5 en Drupal 11.1 o 11.2? No. La tabla de requisitos de PHP de Drupal indica que 8.5 no es compatible con versiones desde 10.4 hasta 11.2. Necesitas 11.3, 11.4 o 12.0, y el orden correcto es actualizar el core primero.

¿Qué hago si un módulo contributed no permite PHP 8.5? Revisa si existe una versión más reciente que ya declare compatibilidad. Si no la hay, queda esperar o mantener un fork propio. No uses composer update --ignore-platform-reqs en producción: solo esconde el conflicto hasta que algo falla en runtime.

¿config.platform debe coincidir con la versión real del servidor? Sí, siempre. config.platform le dice a Composer qué versión asumir al resolver dependencias. Si declaras 8.5 y el servidor corre 8.3, el proyecto resuelve bien en local y falla en producción. Déjalos alineados y verifica con composer check-platform-reqs.

¿array_first() reemplaza a reset() y end()? Cumple la misma función de lectura sin mover el puntero interno del array, que es justo lo que causaba comportamiento inesperado. Conviene usarlo en código nuevo; en código existente no es urgente, pero es de las primeras cosas a tocar cuando buscas código más seguro.

¿Las deprecaciones de PHP 8.5 rompen un sitio en producción? No de inmediato, pero son la señal de lo que se va a romper en PHP 9.0. Las que más atención merecen en Drupal son __sleep() / __wakeup() y el uso de null como índice de array. Revisa el log de errores tras el despliegue, no solo que la página cargue.

Si tu proyecto Drupal tiene módulos a medida o una instalación que停在 11.1 y necesita planear el salto, en Saibher podemos revisar el core, los módulos y la configuración de Composer antes de tocar el hosting. Conversemos con el equipo y lo ordenamos.