Esta entrega, PHP 8.6, introduce mejoras lingüísticas de gran valor, nuevas APIs nativas, capacidades avanzadas para operaciones de I/O y configuraciones de seguridad por defecto mucho más estrictas. Al mismo tiempo, continúa el proceso de limpieza del lenguaje eliminando comportamientos ambiguos y funciones heredadas mediante un conjunto clave de deprecations.
A continuación, analizamos en detalle todos los cambios que debes conocer como desarrollador e ingeniero de software.
1. Aplicación parcial de funciones (Partial Function Application)
Una de las incorporaciones más interesantes de PHP 8.6 es la aplicación parcial de funciones. La idea es sencilla, proporcionar algunos argumentos ahora, dejar otros abiertos y obtener una clausura (closure) de vuelta.
$formatPrice = number_format(?, 2, '.', ' ');
$formatPrice(1299.5); // "1 299.50"
El comodín ? representa un argumento que se suministrará más adelante. También es posible utilizar ... para dejar abiertos los parámetros restantes:
function buildUrl(string $host, string $path, bool $secure = true): string
{
// ...
}
$makePath = buildUrl('example.com', ?, ...);
// fn(string $path, bool $secure = true): string
¿Por qué es relevante?
La aplicación parcial hace que los pipelines de procesamiento sean mucho más limpios, especialmente cuando se combinan con el operador de tubería (pipe operator) de PHP:
$result = $text
|> trim(?)
|> str_replace('_', '-', ?)
|> strtolower(?);
Sin aplicación parcial, cada paso requeriría escribir un closure explícito:
$result = $text
|> (fn($v) => trim($v))
|> (fn($v) => str_replace('_', '-', $v))
|> (fn($v) => strtolower($v));
El resultado es la eliminación del código repetitivo (boilerplate) para centrarse directamente en las transformaciones.
Un detalle de evaluación clave
Los argumentos utilizados para construir una función parcial se evalúan de forma inmediata:
$partial = process(?, getConfig());
En este caso, getConfig() se ejecuta en el momento exacto en el que se crea $partial, no cuando se invoca la clausura resultante. Una función flecha (arrow function) clásica se comporta de manera distinta:
$callback = fn($value) => process($value, getConfig());
Aquí, getConfig() se ejecutaría en cada llamada. Esta diferencia es crítica cuando el valor devuelto depende de un estado cambiante, la hora o datos de la petición.
Limitaciones a tener en cuenta
- No se puede aplicar con el operador
new, accesos mágicos a propiedades (__get()/__set()) o funciones que dependen del ámbito del invocador (comoextract()ofunc_get_arg()). - Una función parcial crea una nueva clausura anónima. No es lo mismo que un First-Class Callable (ej.
strlen(...)): pierde el nombre original y sus atributos.
2. Propiedades readonly con valores por defecto
Las propiedades de solo lectura (readonly) ahora pueden incluir valores por defecto directamente en su definición:
class Config
{
public readonly string $environment = 'production';
}
Esto simplifica el código cuando una propiedad tiene un valor por defecto natural, evitando la necesidad de inicializarla obligatoriamente dentro del constructor.
3. El atributo #[\Override] en constantes de clase
El atributo #[\Override] indica a PHP que un miembro de clase está sobrescribiendo intencionadamente uno definido en la clase padre o interfaz. PHP valida esta regla y emite un error si no existe dicho miembro en el ancestro.
A partir de PHP 8.6, esta verificación también se aplica a las constantes de clase:
class ApiV2 extends Api
{
#[\Override]
public const VERSION = '2.0';
}
Si la clase Api no define la constante VERSION, PHP mostrará un error en tiempo de compilación, facilitando la detección de refactorizaciones o renombres erróneos.
4. Depuración personalizada en enums con __debugInfo()
Los enum ahora pueden implementar el método mágico __debugInfo():
enum Status
{
case Active;
case Inactive;
public function __debugInfo(): array
{
return [
'status' => $this->name,
];
}
}
Esto proporciona a los desarrolladores un control total sobre cómo se muestran los casos de un enum al inspeccionarlos con funciones de depuración como var_dump().
5. Gestión precisa del tiempo con la clase Time\Duration
Uno de los orígenes de errores más comunes en PHP es la confusión sobre las unidades de tiempo que espera cada API:
sleep(2); // Segundos
usleep(500000); // Microsegundos
curl_setopt($handle, CURLOPT_TIMEOUT_MS, 2500); // Milisegundos
Una variable como $timeout = 500; no transmite si representa segundos, milisegundos o microsegundos.
PHP 8.6 introduce la clase \Time\Duration para representar intervalos de tiempo de forma explícita y fuertemente tipada:
use Time\Duration;
$timeout = Duration::fromSeconds(5);
$delay = Duration::fromMilliseconds(500);
Características clave de Time\Duration:
- Estructura interna precisa: Un objeto
Durationse compone internamente de tres componentes (seconds,nanoseconds,negative). Un valor de 1500 ms se almacena como 1 segundo y 500.000.000 nanosegundos. - Operaciones matemáticas directas: Permite combinar y multiplicar duraciones manteniendo el tipo de dato:
$total = Duration::fromSeconds(2)->add(Duration::fromMilliseconds(500)); // 2.5s
$baseDelay = Duration::fromMilliseconds(100);
$attempt = 5;
$delay = $baseDelay->multiplyBy(2 ** $attempt); // Backoff exponencial sin conversiones manuales
- Comparaciones nativas: Se pueden comparar dos duraciones directamente con operadores estándar (
$durationA < $durationB). - Inmutabilidad: Las instancias de
Durationson inmutables. Métodos comoadd()omultiplyBy()devuelven un nuevo objeto, garantizando la seguridad en la reutilización de valores.
6. Nueva función nativa clamp()
PHP 8.6 añade la función clamp() para restringir un valor dentro de un rango mínimo y máximo determinado.
Antes de PHP 8.6:
$volume = 120;
$volume = max(0, min($volume, 100)); // 100
Con clamp():
$volume = clamp($volume, 0, 100); // 100
Funciona con números e incluso con cualquier valor ejecutable de comparación (como objetos DateTimeImmutable):
$min = new DateTimeImmutable('2026-01-01');
$max = new DateTimeImmutable('2026-12-31');
$date = new DateTimeImmutable('2027-02-10');
$date = clamp($date, $min, $max);
echo $date->format('Y-m-d'); // 2026-12-31
7. Precisión en la ubicación de errores JSON
Históricamente, al decodificar JSON con la opción JSON_THROW_ON_ERROR, PHP notificaba el tipo de error pero no el lugar exacto del fallo en el documento.
PHP 8.6 añade la posición exacta donde ocurre la falla de parseo:
// Si la excepción no se captura:
Fatal error: Uncaught JsonException: Syntax error near location 1:3
// Si la excepción se captura:
Syntax error near location 1:3
8. El enum estándar SortDirection
Se introduce un enum nativo para estandarizar la dirección de ordenamiento en repositorios y librerías:
enum SortDirection {
case Ascending;
case Descending;
}
Ejemplo práctico en una capa de repositorio:
class UserRepository
{
public function findAll(SortDirection $direction): array
{
$order = match ($direction) {
SortDirection::Ascending => 'ASC',
SortDirection::Descending => 'DESC',
};
return $this->db->query("SELECT * FROM users ORDER BY created_at {$order}")->fetchAll();
}
}
$users = $repository->findAll(SortDirection::Descending);
9. Inclusión de Form-Feed (\f) en la familia trim()
Las funciones trim(), ltrim() y rtrim() ahora incluyen el carácter de control Form-Feed (\f) dentro de su lista de caracteres de espacio en blanco a eliminar por defecto:
$value = "\fHello World\f";
echo trim($value); // "Hello World"
10. Nueva polling API para I/O multiplexing
Para sistemas de alto rendimiento y servidores que manejan miles de conexiones concurrentes, PHP 8.6 sustituye las limitaciones históricas de stream_select() (basado en el comando del sistema select()) mediante una Polling API unificada.
La Polling API selecciona automáticamente el mecanismo nativo más eficiente según el sistema operativo subyacente (epoll en Linux, kqueue en macOS/BSD, WSAPoll en Windows, etc.):
use Io\Poll\Context;
use Io\Poll\Event;
use Time\Duration;
$context = new Context();
$stream = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
stream_set_blocking($stream, false);
// StreamPollHandle reside en el espacio de nombres global
$handle = new StreamPollHandle($stream);
$context->add($handle, [Event::Read]);
while (true) {
// Espera hasta 1 segundo utilizando la nueva clase Duration
$watchers = $context->wait(Duration::fromSeconds(1));
foreach ($watchers as $watcher) {
if ($watcher->hasTriggered(Event::Read)) {
$client = stream_socket_accept($stream, 0);
// Procesamiento de la nueva conexión...
}
}
}
La Polling API no busca sustituir a un bucle de eventos completo (event loop), sino proporcionar el componente de bajo nivel sobre el cual frameworks puede construir abstracciones asíncronas masivas.
11. Seguridad reforzada en sesiones por defecto
PHP 8.6 modifica los valores predeterminados de gestión de sesiones para adoptar estándares seguros desde el primer momento:
session.use_strict_mode = 1: Previene ataques de fijación de sesión rechazando IDs de sesión no inicializados por el servidor.session.cookie_httponly = 1: Impide que JavaScript en el cliente acceda a la cookie de sesión (protección frente a XSS).session.cookie_samesite = Lax: Protege frente a ataques de falsificación de petición en sitios cruzados (CSRF).
12. Deprecations y migraciones
PHP 8.6 continúa limpiando APIs obsoletas. A continuación se resumen las funciones sustituidas y sus reemplazos oficiales:
| Patrón / Función Deprecada | Reemplazo Recomendado |
is_double($v) | is_float($v) |
is_integer($v) / is_long($v) | is_int($v) |
doubleval($v) | floatval($v) |
spl_object_hash($obj) | spl_object_id($obj) (Nota: Devuelve int, no string) |
spl_classes() | (new ReflectionExtension('spl'))->getClassNames() |
strcoll($a, $b) | Collator::compare() (Requiere la extensión intl) |
metaphone($s) | Utilizar librerías de concordancia fonética en espacio de usuario (userland) |
return dentro de bloque finally | Eliminar el return en finally (solo realizar tareas de limpieza) |
Retornar valor desde __construct() o __destruct() | No retornar valores (un return; vacío para salir temprano es válido) |
array_walk($obj, ...) | $vars = get_object_vars($obj); array_walk($vars, ...); |
Además, palabras como let, is y readonly quedan reservadas y no podrán utilizarse como nombres de clases o funciones.
Estrategia recomendada de migración
Para afrontar la actualización a PHP 8.6 en un entorno de producción, recomendamos aplicar el siguiente flujo de trabajo progresivo:
- Escaneo de código: Ejecutar PHPCompatibility en tu CI/CD para detectar usos obsoletos en todo el árbol de código.
- Reescritura automatizada: Utilizar Rector para aplicar parches automáticos a las llamadas deprecadas (ej. reescribir
is_integerais_int). - Análisis estático: Configurar PHPStan con sus reglas de deprecations y congelar la línea base (baseline) para evitar la introducción de código antiguo.
- Validación en tests: Comprobar la suite de pruebas unitarias y de integración sobre la versión objetivo para verificar que no existan desviaciones de comportamiento en tiempo de ejecución.
Referencias:
¿Qué te ha parecido?