---
title: 'PHP 8.1: Enums, Fibers, Readonly & Intersection Types — PHP Upgrade Guide | DevSense'
description: 'PHP 8.1 después de 8.0: enums, propiedades readonly, fibers, tipos de intersección/never, first-class callables—además de reglas para $GLOBALS, excepciones de MySQLi y lista de depreciaciones.'
faq:
    - { question: '¿Qué son los Enums en PHP 8.1 y cómo se utilizan?', answer: 'Los Enums son una forma de tipo seguro de definir un conjunto cerrado de valores posibles. Los backed enums asignan casos a valores string o int para la serialización, mientras que los pure enums son solo de identidad y representan estados abstractos.' }
    - { question: '¿Cuáles son las reglas para las Propiedades Readonly en PHP 8.1?', answer: 'Las propiedades readonly deben estar tipadas, no pueden tener valores por defecto y solo se pueden inicializar una vez (normalmente en el constructor). Los intentos posteriores de modificarlas lanzarán un Fatal Error.' }
    - { question: '¿Cómo funciona la sintaxis first-class callable en PHP 8.1?', answer: 'Permite crear una Closure a partir de cualquier función o método llamable usando tres puntos `...` (por ejemplo, `strlen(...)`), reemplazando la sintaxis anterior y más verbosa `Closure::fromCallable()`.' }
    - { question: '¿Para qué se utiliza el tipo de retorno never?', answer: 'El tipo `never` indica que una función nunca devolverá un valor. Esto significa que debe llamar a `exit()`, lanzar una excepción o entrar en un bucle infinito, lo que permite un mejor análisis estático.' }
published: '2026-05-31'
---
# PHP 8.1: Características Principales

Ruta de actualización desde PHP 8.0.x: adiciones al lenguaje, endurecimiento del tiempo de ejecución y listas de verificación de migración para staging.

## Índice
* [Enums (Enumeraciones)](#enums)
* [Propiedades Readonly](#readonly-properties)
* [Sintaxis First-class callable](#first-class-callable)
* [Fibers](#fibers)
* [Tipos de Intersección](#intersection-types)
* [Tipo de retorno never](#never-type)
* [new en inicializadores](#new-in-initializers)
* [Ajustes sintácticos y de lenguaje (0o, desempaquetado de arrays, argumentos nombrados después de desempaquetar)](#syntax-tweaks)
* [Cambios incompatibles con versiones anteriores (notas de migración)](#backward-incompatible)
* [Depreciaciones (qué corregir temprano)](#deprecations)
* [Extensiones: cambios notables](#extensions-changes)
* [Errores Comunes](#common-mistakes)
* [Quiz de Autoevaluación](#self-test-quiz)

---

Mientras que PHP 8.0 fue un reinicio importante del motor de tiempo de ejecución, PHP 8.1 se centra en pulir la semántica del lenguaje y proporcionar construcciones de sintaxis modernas. Al introducir Enums nativos, propiedades de solo lectura (readonly) y tipos de intersección directamente en el sistema de tipos, PHP 8.1 permite a los desarrolladores escribir código declarativo y de tipos seguros con mucho menos código repetitivo. Sin embargo, esta versión también endurece los supuestos del tiempo de ejecución: restringe las operaciones de escritura en `$GLOBALS`, obliga a MySQLi a lanzar excepciones por defecto y marca docenas de comportamientos heredados como depreciados. Aquí tiene una guía probada para comprender estas características y navegar con éxito por la actualización.

<a id="enums"></a>
## Enums (Enumeraciones)

Los Enums aportan una forma nativa y de tipos seguros para representar un conjunto cerrado de valores.

* **Backed enums** almacenan un valor escalar (`string|int`) que se puede serializar/transportar fácilmente.
* **Pure enums** son solo de identidad, ideales para máquinas de estado de dominio y manejo exhaustivo.

> [!NOTE]
> **¿Sabía que?**
> ¡Los Enums están implementados internamente como clases! Puede implementar interfaces, definir métodos, escribir métodos estáticos y usar traits (con la limitación de que los traits no pueden contener propiedades) en sus Enums al igual que en las clases normales.

```php
// app/Enums/Status.php
enum Status: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}

// app/Services/PostService.php
function canEdit(Status $status): bool
{
    return $status !== Status::Archived;
}

$status = Status::from('draft');  // Status::Draft
$value  = $status->value;         // 'draft'
```

### Orientación práctica

* Use enums en lugar de banderas tipadas como cadenas (`'draft'|'published'`) en las firmas de métodos y DTOs.
* Prefiera backed enums para persistencia y APIs; prefiera pure enums para estados de flujo de trabajo internos.
* Combínelos con `match` para un manejo exhaustivo:

```php
// app/Services/PostService.php
function label(Status $status): string
{
    return match ($status) {
        Status::Draft => 'Draft',
        Status::Published => 'Published',
        Status::Archived => 'Archived',
    };
}
```

---

<a id="readonly-properties"></a>
## Propiedades Readonly

Las propiedades de solo lectura (readonly) se pueden asignar **solo una vez**, normalmente en el constructor. Esto es una gran ventaja para DTOs inmutables, objetos de valor (value objects) y modelos de dominio más seguros.

> [!NOTE]
> **¿Sabía que?**
> ¡Una propiedad readonly no puede tener un valor por defecto! Si intenta escribir `public readonly string $status = 'draft';`, PHP lanzará un error fatal en tiempo de compilación.

```php
// app/DTO/UserProfile.php
final class UserProfile
{
    public function __construct(
        public readonly int $id,
        public readonly string $email,
    ) {}
}
```

### Qué cambia esto arquitectónicamente

* Fomenta los objetos del tipo \"construir completamente, luego no mutar\".
* Reduce la necesidad de propiedades privadas + getters para muchas estructuras similares a DTOs.
* Funciona muy bien con la promoción de propiedades del constructor para tipos inmutables concisos.

---

<a id="first-class-callable"></a>
## Sintaxis First-class callable

PHP 8.1 agrega una forma conveniente de crear clausuras (closures) a partir de llamables:

```php
// app/Helpers/StringHelper.php
function normalize(string $s): string
{
    return strtolower(trim($s));
}

// app/Services/DataService.php
$fn = normalize(...);               // Closure
$out = array_map($fn, [' A ', 'B ']); // ['a', 'b']
```

Esto equivale a `Closure::fromCallable('normalize')`, pero es más corto y se lee mejor en tuberías (pipelines).

---

<a id="fibers"></a>
## Fibers

Las Fibers son una primitiva de bajo nivel para **concurrencia cooperativa**. No hacen que su código sea asíncrono mágicamente, pero permiten que los frameworks/bibliotecas construyan entornos de ejecución asíncronos, planificadores y concurrencia estructurada.

```php
// app/Services/FiberService.php
$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('pausado');
    echo "reanudado con: {$value}\n";
});

echo $fiber->start() . "\n"; // pausado
$fiber->resume('payload');   // reanudado con: payload
```

### Cuándo debería importarle

* Si usa un framework asíncrono o un bucle de eventos (ReactPHP, Amp): las Fibers simplificarán significativamente la integración.
* Para la mayoría de las aplicaciones clásicas de solicitud/respuesta: las Fibers son principalmente \"infraestructura para bibliotecas\".

---

<a id="intersection-types"></a>
## Tipos de Intersección

Los tipos de intersección expresan \"debe cumplir con todas estas interfaces/clases\".

```php
// app/Services/ExporterService.php
function export(iterable&Countable $items): array
{
    if (count($items) === 0) {
        return [];
    }

    return iterator_to_array($items, preserve_keys: true);
}
```

Nota: los tipos de intersección no se pueden combinar con tipos de unión en PHP 8.1 (los tipos DNF se introducen más adelante en PHP 8.2).

---

<a id="never-type"></a>
## Tipo de retorno never

El tipo de retorno `never` indica que una función **no regresa**: siempre lanza una excepción o termina la ejecución.

```php
// app/Helpers/ErrorHelper.php
function fail(string $message): never
{
    throw new RuntimeException($message);
}
```

Esto mejora el análisis estático y hace explícito el flujo de control.

---

<a id="new-in-initializers"></a>
## new en inicializadores

PHP 8.1 permite expresiones `new` en más lugares: valores por defecto de parámetros, variables estáticas, inicializadores de constantes y argumentos de atributos.

```php
// app/Services/Clock.php
final class Clock
{
    public function now(): DateTimeImmutable
    {
        return new DateTimeImmutable('now');
    }
}

// app/Services/HandlerService.php
function handler(Clock $clock = new Clock()): DateTimeImmutable
{
    return $clock->now();
}
```

Esto elimina gran parte de la redundancia para fábricas en casos simples. (Tenga en cuenta la vida útil de los servicios si está en un contenedor de inyección de dependencias).

---

<a id="syntax-tweaks"></a>
## Ajustes sintácticos y de lenguaje

### Prefijo literal de entero octal: `0o` / `0O`

```php
// app/Config/FileConfig.php
$mask = 0o755;
```

### Desempaquetado de arrays con llaves de cadena

```php
// app/Services/ArrayService.php
$a = [1, 'a' => 'b'];
$b = [...$a, 'c' => 'd']; // [1, 'a' => 'b', 'c' => 'd']
```

### Argumento nombrado después del desempaquetado de argumentos

```php
// app/Services/ArgumentService.php
foo(...$args, named: $arg);
```

### `full_path` para cargas de archivos

Las entradas de `$_FILES` ahora incluyen una clave `full_path` (destinada a cargas de directorios con `webkitdirectory`).

---

<a id="backward-incompatible"></a>
## Cambios Incompatibles con Versiones Anteriores (notas de migración)

Esta sección es intencionadamente práctica: se centra en problemas que suelen romper las aplicaciones durante la actualización.

### Núcleo de PHP / lenguaje

* **Restricciones de escritura en $GLOBALS**: ya no se admite escribir en todo el array `$GLOBALS` (por ejemplo, `array_pop($GLOBALS)` genera errores). El acceso a elementos individuales como `$GLOBALS['x']` sigue funcionando.
* **Variables estáticas en métodos heredados**: los métodos heredados (no sobreescritos) ahora comparten variables estáticas con el método padre.
* **Parámetros opcionales antes de los requeridos**: los parámetros opcionales declarados antes de los requeridos se tratan como requeridos; en PHP 8.1, esto ahora puede generar un `ArgumentCountError` en tiempo de llamada (incluso con argumentos nombrados).
* **Compatibilidad de tipo de retorno con clases internas**: sobreescribir métodos internos sin tipos de retorno compatibles genera advertencias de depreciación; use `#[ReturnTypeWillChange]` donde sea necesario para compatibilidad entre versiones.
* **Nuevas palabras clave**:
  * `readonly` es ahora una palabra clave (aún se permite como nombre de función).
  * `never` es ahora una palabra reservada (no se puede usar para nombres de clases/interfaces/traits ni en espacios de nombres).

### Migración de recursos a objetos

Varias extensiones migraron recursos a objetos (actualice las comprobaciones `is_resource()` por comprobaciones de tipo de objeto o falsedad):

* **FileInfo**: usa objetos `finfo` en lugar de recursos de fileinfo.
* **FTP**: usa objetos `FTP\Connection`.
* **IMAP**: usa objetos `IMAP\Connection`.
* **LDAP**: usa objetos `LDAP\Connection`, `LDAP\Result`, `LDAP\ResultEntry`.
* **PgSQL**: usa objetos `PgSql\Connection`, `PgSql\Result`, `PgSql\Lob`.
* **PSpell**: usa objetos `PSpell\Dictionary` y `PSpell\Config`.

### Cambios de comportamiento en PDO / MySQLi

* **MySQLi**: el modo predeterminado de reporte de errores cambió de \"silencioso\" a \"excepciones\". Para restaurar el comportamiento anterior: `mysqli_report(MYSQLI_REPORT_OFF);`
* **PDO**: el comportamiento de `PDO::ATTR_STRINGIFY_FETCHES` cambió para booleanos; los controladores SQLite/MySQL ahora devuelven enteros/flotantes como tipos nativos de PHP de manera más consistente.

### Cambios de comportamiento de la biblioteca estándar

* `version_compare()` ya no acepta abreviaturas de operadores no documentadas.
* Las funciones de escape HTML ahora tienen el valor predeterminado `ENT_QUOTES | ENT_SUBSTITUTE` (se escapan las comillas; el UTF-8 mal formado se reemplaza).

---

<a id="deprecations"></a>
## Depreciaciones (qué corregir temprano)

Las depreciaciones en 8.1 son \"futuros errores\". Corregirlas temprano vale la pena cuando más tarde se actualice a PHP 8.2+.

### Depreciaciones del núcleo

* Pasar `null` a parámetros no nulos de funciones integradas está depreciado.
* Las conversiones implícitas de float a int con pérdida de precisión están depreciadas (claves de arrays, declaraciones de tipo int en modo coercitivo, operadores int).
* Llamar a un elemento estático en un trait está depreciado.
* La autovivificación a partir de `false` está depreciada (`$x = false; $x[] = 1;`).

### Depreciaciones notables de extensiones

* **Date**: `strftime()` / `gmstrftime()` y `strptime()` están depreciadas (prefiera `date()` / `IntlDateFormatter`).
* **Filter**: `FILTER_SANITIZE_STRING` y `FILTER_SANITIZE_STRIPPED` están depreciadas.
* **Hash**: las funciones `mhash*()` están depreciadas (use `hash_*()`).
* **PDO**: `PDO::FETCH_SERIALIZE` está depreciada.

---

<a id="common-mistakes"></a>
## ⚠️ Errores Comunes

Aquí tiene algunos problemas comunes al usar las características de PHP 8.1:

### 1. Modificar Propiedades Readonly Internamente o en Subclases
Una propiedad `readonly` no se puede modificar incluso desde dentro de la clase o por una subclase una vez que se ha inicializado.

```php
// app/DTO/UserProfile.php
// MALO: Modificar una propiedad readonly después de la inicialización causa un Error
class UserProfile {
    public function __construct(public readonly string $email) {}

    public function updateEmail(string $newEmail): void {
        $this->email = $newEmail; // Fatal Error: Cannot modify readonly property
    }
}
```

### 2. Autovivificación desde False
En PHP 8.1, intentar crear automáticamente un array a partir de un valor booleano establecido en `false` está depreciado.

```php
// app/Services/ArrayService.php
// MALO: Provoca una advertencia de depreciación en PHP 8.1 (y un Fatal Error en PHP 8.2)
$data = false;
$data[] = 'value';

// BUENO: Inicialice primero como un array vacío
$data = [];
$data[] = 'value';
```

### 3. Reasignar el Array $GLOBALS
Puede modificar variables dentro de `$GLOBALS`, pero no puede reasignar o destruir la variable `$GLOBALS` en sí misma.

```php
// app/Services/LegacyService.php
// MALO: Fatal Error en PHP 8.1
$GLOBALS = []; 

// MALO: Re-vincular referencias es ilegal
$GLOBALS = &$someOtherArray;
```

---

<a id="self-test-quiz"></a>
## Quiz de Autoevaluación

Pruebe su comprensión de las características de PHP 8.1.

### Pregunta 1: ¿Cuál de las siguientes afirmaciones es verdadera sobre las propiedades Readonly de PHP 8.1?
* A) Se pueden reasignar siempre que el nuevo valor tenga el mismo tipo.
* B) Solo se pueden declarar en propiedades tipadas (no en propiedades sin tipo).
* C) Se les puede asignar un valor por defecto en la definición de la clase.

<details>
<summary>Mostrar respuestas</summary>

**Respuesta: B**  
En PHP 8.1, `readonly` solo se puede aplicar a propiedades tipadas. Declarar una propiedad readonly sin tipo es un error de sintaxis. Además, no pueden tener valores por defecto y nunca se pueden reasignar después de la inicialización.
</details>

### Pregunta 2: ¿Qué sucede si intenta construir automáticamente un array a partir de una variable que actualmente es `false` (por ejemplo, `$x = false; $x[] = 1;`) en PHP 8.1?
* A) Funciona sin problemas, convirtiendo la variable en un array.
* B) Desencadena una advertencia de depreciación.
* C) Lanza un `Fatal Error` inmediatamente.

<details>
<summary>Mostrar respuestas</summary>

**Respuesta: B**  
En PHP 8.1, la autovivificación a partir de `false` está depreciada y desencadena una advertencia. En PHP 8.2, esta advertencia se promueve a un Fatal Error.
</details>

---

## Resumen

Mientras que PHP 8.0 fue un reinicio técnico, PHP 8.1 pule la semántica y aporta construcciones modernas. Mediante Enums, propiedades Readonly y tipos de intersección incorporados directamente en el sistema de tipos, escribirá código más limpio y con mayor seguridad de tipos utilizando significativamente menos código repetitivo.