---
title: 'PHP 8.0 from PHP 7.4: JIT, Union Types, Match & Nullsafe — Upgrade Guide | DevSense'
description: 'Explore PHP 8.0 después de 7.4: argumentos nombrados, match, nullsafe, atributos, JIT, tipos unión—qué se rompe, qué reescribir y ejemplos ejecutables.'
faq:
    - { question: '¿Cuál es el beneficio principal del compilador JIT en PHP 8.0?', answer: 'El compilador JIT (Just-In-Time) traduce el bytecode a código máquina en tiempo de ejecución, ofreciendo hasta un 500% de aumento de rendimiento para tareas pesadas limitadas por la CPU, aunque las aplicaciones web típicas ganan entre un 1% y un 5%.' }
    - { question: '¿En qué se diferencia la expresión match de una sentencia switch clásica?', answer: "Match utiliza una comparación de identidad estricta (===), devuelve un valor directamente, no tiene comportamiento de caída (fall-through, por lo que no se necesita 'break') y lanza un UnhandledMatchError si ningún valor coincide y no se proporciona un default." }
    - { question: '¿Qué es la promoción de propiedades del constructor en PHP 8.0?', answer: 'La promoción de propiedades del constructor permite a los desarrolladores declarar e inicializar las propiedades de la clase directamente en la lista de parámetros del constructor, eliminando la necesidad de declaraciones y asignaciones de propiedades separadas.' }
    - { question: '¿Cómo cambiaron las comparaciones flexibles en PHP 8.0?', answer: 'En PHP 8.0, las comparaciones no estrictas entre números y cadenas no numéricas los comparan como cadenas de texto en lugar de convertir la cadena a 0, lo que evita errores como que ''0 == "foobar"'' devuelva true.' }
published: '2026-05-31'
---
# PHP 8.0: Características Principales

Desde PHP 7.4: notas basadas en benchmarks sobre JIT, tipos y los cambios incompatibles que surgen en aplicaciones reales.

## Índice
* [Argumentos Nombrados (Named Arguments)](#named-arguments)
* [Expresión Match](#match-expression)
* [Operador Nullsafe (?->)](#nullsafe-operator)
* [Promoción de Propiedades del Constructor](#constructor-promotion)
* [Evolución del Sistema de Tipos (Unión, mixed, static)](#type-system)
* [Atributos / Anotaciones](#attributes)
* [Nuevas Funciones de Cadena](#string-functions)
* [Weak Maps](#weak-maps)
* [Expresión Throw y Manejo de Errores](#throw-and-errors)
* [Compilador JIT y Rendimiento](#jit-performance)
* [Cambios en Módulos y el Núcleo](#modules-changes)
* [Ajustes Sintácticos (::class, comas finales)](#syntax-tweaks)
* [Comparaciones de Cadena a Número más Sanas](#saner-comparisons)
* [Cambios Incompatibles con Versiones Anteriores (Notas de Migración)](#backward-incompatible)
* [Errores Comunes](#common-mistakes)
* [Quiz de Autoevaluación](#self-test-quiz)

---

Actualizar a una nueva versión principal de un lenguaje siempre es una apuesta de alto riesgo: desea las nuevas mejoras de rendimiento, pero teme las caídas de producción a medianoche causadas por supuestos heredados silenciosos. PHP 8.0 no es solo una actualización menor típica—es un reinicio fundamental de la plataforma. Al introducir el compilador JIT, convertir las conversiones de tipo flexibles en comparaciones estrictas y transformar las advertencias silenciosas en errores fatales (Errors) que interrumpen el script, PHP 8.0 exige un cambio total en la forma en que escribimos y migramos nuestro código. Aquí tiene un desglose práctico y probado de qué cambia bajo el capó y qué se romperá realmente en su aplicación.

<a id="named-arguments"></a>
## Argumentos Nombrados

Pasar argumentos a una función por su nombre elimina la necesidad de recordar su orden y permite omitir parámetros opcionales. Esto hace que el código sea autodocumentado.

> [!NOTE]
> **¿Sabía que?**
> Desde PHP 8.0, si usa argumentos nombrados, cambiar el nombre de un parámetro en una clase padre o interfaz es un cambio incompatible. Si una clase hija o un llamador usa el nombre del parámetro antiguo, ¡lanzará un `ArgumentCountError` o un `Error`!

```php
// Antes: tenía que especificar todos los parámetros en orden
// app/Services/CookieService.php
setcookie('test', '', time() + 60 * 60 * 2, '/', '', false, true);

// PHP 8.0: especifique solo lo requerido
// app/Services/CookieService.php
setcookie(
    name: 'test',
    expires: time() + 60 * 60 * 2,
    httponly: true
);
```

---

<a id="match-expression"></a>
## Expresión Match

Una alternativa más estricta y concisa a la sentencia switch. Devuelve un valor y utiliza una comparación estricta (===), eliminando errores inesperados con la conversión implícita de tipos.

```php
// app/Services/ResponseService.php
$statusCode = 200;

$statusMessage = match ($statusCode) {
    200, 300 => 'Éxito o Redirección',
    400, 404 => 'Error del Cliente',
    500 => 'Error del Servidor',
    default => 'Estado desconocido',
};
```

---

<a id="nullsafe-operator"></a>
## Operador Nullsafe (?->)

Permite leer propiedades y llamar a métodos en cadena. Si uno de los elementos es nulo, toda la cadena devuelve nulo sin lanzar un error fatal.

```php
// PHP 7.4
// app/Services/SessionService.php
$country = null;
if ($session !== null) {
    $user = $session->user;
    if ($user !== null) {
        $country = $user->getAddress()->country;
    }
}

// PHP 8.0
// app/Services/SessionService.php
$country = $session?->user?->getAddress()?->country;
```

---

<a id="constructor-promotion"></a>
## Promoción de Propiedades del Constructor

Anteriormente, la creación de DTO (Data Transfer Objects) simples o Value Objects requería escribir mucho código repetitivo: declarar propiedades, pasarlas al constructor y asignarlas. PHP 8.0 combina estos tres pasos en uno solo.

```php
// PHP 7.4: Enfoque clásico (y detallado)
// app/DTO/UserDTO.php
class UserDTO 
{
    public string $name;
    public string $email;
    protected int $age;

    public function __construct(string $name, string $email, int $age) 
    {
        $this->name = $name;
        $this->email = $email;
        $this->age = $age;
    }
}

// PHP 8.0: Elegante y conciso
// app/DTO/UserDTO.php
class UserDTO 
{
    public function __construct(
        public string $name,
        public string $email,
        protected int $age,
    ) {}
}
```

---

<a id="type-system"></a>
## Evolución del Sistema de Tipos

In PHP 8.0, el sistema de tipos se ha vuelto significativamente más estricto y expresivo.

### Tipos Unión
Antes de PHP 8.0, si una variable podía aceptar múltiples tipos de datos (por ejemplo, `int` o `float`), dependíamos de las anotaciones PHPDoc. Ahora, PHP admite esto de forma nativa.

```php
// app/Services/CalculatorService.php
class Calculator 
{
    private int|float $number;

    public function setNumber(int|float $number): void 
    {
        $this->number = $number;
    }
}
```

### El pseudo-tipo `mixed`

El nuevo tipo `mixed` resuelve problemas de código heredado. Equivale a `array|bool|callable|int|float|null|object|resource|string`. Nota: `mixed` ya incluye nulo, por lo que escribir `?mixed` o `mixed|null` no está permitido y generará un error de compilación.

### El tipo de retorno `static`

Para implementar patrones como \"Late Static Binding\" (enlace estático tardío) e \"Interfaces Fluídas\", se ha agregado el tipo de retorno `static` (anteriormente, solo estaba disponible `self`).

```php
// app/Factories/BaseFactory.php
class BaseFactory {
    public function create(): static {
        return new static();
    }
}
```

### La interfaz `Stringable`

Si una clase implementa el método mágico `__toString()`, PHP 8.0 le asigna automáticamente (de forma implícita) la interfaz `Stringable`. Esto permite usar `string|Stringable` en las indicaciones de tipo.

---

<a id="attributes"></a>
## Atributos / Anotaciones (Análisis Profundo)

Los **atributos** (introducidos en PHP 8.0) son **metadatos estructurados** que se adjuntan a clases, métodos, propiedades, parámetros y constantes. El motor los almacena en el bytecode; usted los lee con la **Reflexión**. **No son mágicos**: no pasa nada a menos que *su* framework, enrutador o herramienta los inspeccione; es la misma idea que las anotaciones de Java/C#, pero nativa de PHP.

### Atributos frente a PHPDoc

| | PHPDoc (`@route`, `@deprecated`) | Atributos (`#[Route]`) |
|---|----------------------------------|-------------------------|
| Análisis | Cadenas en comentarios; requiere analizadores personalizados | Sintaxis nativa; sin expresiones regulares sobre comentarios |
| Tipado | Informal; fácil de desviar del código | Los argumentos del constructor son valores reales de PHP |
| Herramientas | El soporte de IDE varía | La API de Reflexión es estable y rápida |

Use PHPDoc para la **documentación humana**; use atributos para el **comportamiento que su código realmente leerá** (enrutamiento, sugerencias de validación, serialización, generación de código).

### Declarar una clase de atributo

Cualquier clase se puede usar como atributo si está marcada con el atributo integrado `#[\Attribute]`. Una máscara de bits opcional limita **dónde** puede aparecer (`Attribute::TARGET_*`). Omitir los objetivos hace que se aplique de forma predeterminada a **todos** (normalmente querrá ser explícito).

```php
// app/Attributes/Route.php
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
final class Route
{
    public function __construct(
        public string $path,
        public array $methods = ['GET'],
    ) {}
}
```

* **`final`** es común para que la clase de atributo no se herede accidentalmente.
* Los parámetros del constructor se convierten en los **argumentos** pasados en el lugar de uso (`#[Route('/x', methods: [...])]`).

También puede marcar un atributo como **repetible** en el mismo elemento (por ejemplo, múltiples validadores) con `Attribute::IS_REPEATABLE` en la máscara de bits.

### Aplicar atributos (sintaxis)

Los atributos se colocan **antes** de la declaración, uno o más por línea o agrupados:

```php
// app/Http/Controllers/UserController.php
#[Route('/api/users', methods: ['GET', 'POST'])]
final class UserController {}

// app/Http/Controllers/ItemController.php
#[Route('/items')]
final class ItemController
{
    #[Route('/items/{id}', methods: ['GET'])]
    public function show(int $id): array
    {
        return ['id' => $id];
    }
}
```

Los argumentos nombrados (PHP 8.0) se leen de forma natural: `methods: ['GET']` se asigna al parámetro del constructor.

### Cómo funciona en tiempo de ejecución

1. PHP **analiza** los atributos en tiempo de compilación y los asocia con la estructura de reflexión.
2. En tiempo de ejecución, usted obtiene un `ReflectionClass`, `ReflectionMethod`, `ReflectionProperty`, etc.
3. Llame a **`getAttributes()`** para obtener instancias de `ReflectionAttribute`, luego a **`newInstance()`** para instanciar su clase de atributo con los argumentos del código fuente.

Nada se ejecuta por sí mismo: su bootstrap, contenedor de inyección de dependencias o enrutador debe llamar a Reflexión (o a una biblioteca que la envuelva).

### Leer atributos con Reflexión

```php
// app/Services/RouterService.php
$reflection = new ReflectionClass(UserController::class);

// Solo atributos de una clase dada (recomendado)
foreach ($reflection->getAttributes(Route::class) as $attr) {
    /** @var Route $route */
    $route = $attr->newInstance();
    // $route->path, $route->methods
}

// Todos los atributos de la clase (fíltrelo usted mismo)
foreach ($reflection->getAttributes() as $attr) {
    $name = $attr->getName();       // ej. Route::class
    $args = $attr->getArguments(); // argumentos crudos del constructor
}
```

Use **`ReflectionMethod::getAttributes()`** para rutas o middleware a nivel de método, **`ReflectionParameter::getAttributes()`** para atributos de validación de parámetros, etc.

---

<a id="saner-comparisons"></a>
## Comparaciones de Cadena a Número más Sanas

Este es uno de los cambios incompatibles más sutiles. Anteriormente, al realizar una comparación no estricta entre un número y una cadena, PHP convertía la cadena a un número. En PHP 8.0, si la cadena no es numérica, los números se comparan como cadenas.

```php
// app/Services/ComparisonService.php
// PHP 7.4
0 == 'foobar'; // true

// PHP 8.0
0 == 'foobar'; // false
```

---

<a id="syntax-tweaks"></a>
## Ajustes Sintácticos

### Permitir `::class` en objetos

Ahora puede obtener el nombre de la clase directamente desde una variable de objeto usando `::class`. Anteriormente, se usaba la función `get_class()` para esto.

```php
// app/Services/ReflectionService.php
$object = new \App\Models\User();
// PHP 7: get_class($object);
echo $object::class; // Muestra \"App\\Models\\User\"
```

### Comas finales

Se permite dejar una coma final en las listas de parámetros de funciones, métodos y clausuras (closures). Esto hace que las confirmaciones de Git sean más limpias.

```php
// app/Services/RequestService.php
public function makeRequest(
    string $url,
    array $data,
    array $headers, // <-- la coma final ahora es legal
) { ... }
```

---

<a id="string-functions"></a>
## Nuevas Funciones de Cadena

En lugar de usar `strpos()` y verificar `!== false`, se han agregado tres nuevas funciones que devuelven un `bool` estricto:

```php
// app/Services/StringService.php
$str = "DevSense es genial";

str_starts_with($str, "DevSense"); // true
str_contains($str, "genial");      // true
str_ends_with($str, "genial");     // true
```

### La función `get_debug_type()`

La nueva función `get_debug_type()` devuelve un tipo útil de una variable (por ejemplo, `App\Models\User` en lugar de simplemente `object`, o `int` en lugar de `integer`). Es perfecta para compilar mensajes de error claros.

---

<a id="weak-maps"></a>
## Weak Maps

Un gran avance arquitectónico para `ORMs` y `caching`. `WeakMap` permite crear enlaces a objetos de una manera que **no impide** que el Garbage Collector elimine esos objetos de la memoria.

```php
// app/Services/CacheService.php
class Cache {
    private WeakMap $cache;
    
    public function __construct() {
        $this->cache = new WeakMap();
    }
    
    public function getMetadata(object $obj): array {
        if (!isset($this->cache[$obj])) {
            $this->cache[$obj] = $this->computeExpensiveData($obj);
        }
        return $this->cache[$obj];
    }
}
```

Tan pronto como el objeto `$obj` se destruye en la aplicación, desaparece automáticamente también del WeakMap, liberando memoria.

---

<a id="throw-and-errors"></a>
## Expresión Throw y Manejo de Errores

El operador throw ha cambiado de una sentencia a una expresión.

```php
// app/Services/UserService.php
// Ahora puede lanzar excepciones directamente en operadores ternarios o con fusión nula
$user = $request->get('user') ?? throw new InvalidArgumentException('El usuario es requerido');

$callable = fn() => throw new Exception('Esto no debería ejecutarse');
```

Cambio de rigor global: en PHP 8.0, el operador de supresión de errores `@` ya no silencia los errores fatales.
Además, la mayoría de las advertencias del motor (Engine Warnings) se han convertido en excepciones de error (por ejemplo, intentar acceder a una propiedad de un elemento no-objeto ahora hace que el script falle en lugar de simplemente registrar una advertencia).

### Capturas no capturadoras (Non-capturing catches)

Si necesita capturar una excepción, pero no necesita el objeto de la excepción en sí, ahora se puede omitir la variable.

```php
// app/Services/ErrorService.php
// Antes: estábamos obligados a declarar la variable $e
try {
    // código
} catch (Exception $e) {
    Log::error('Algo salió mal');
}

// PHP 8.0:
try {
    // código
} catch (Exception) {
    Log::error('Algo salió mal');
}
```

### Errores de tipo para funciones internas

La mayoría de las funciones internas de PHP ahora lanzan excepciones estrictas de tipo `TypeError` o `ValueError` cuando se les pasan parámetros no válidos, en lugar de emitir un Warning y devolver null.

```php
// app/Services/DebugService.php
// PHP 7.4
strlen([]); // Warning: strlen() expects parameter 1 to be string, array given

// PHP 8.0
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
```

---

<a id="jit-performance"></a>
## Compilador JIT y Rendimiento

El cambio arquitectónico más fundamental bajo el capó de PHP 8.0 es la introducción del `compilador JIT (Just-In-Time)`.

**OPcache** (Antes de PHP 8): el código fuente se analizaba en OpCodes y la máquina virtual Zend los ejecutaba línea por línea.

**JIT (PHP 8.0+)**: analiza los OpCodes y compila las rutas críticas ("hot paths") directamente a código máquina para la CPU (x86/ARM).

Para aplicaciones web típicas limitadas por E/S (I/O-bound), la ganancia de rendimiento es de alrededor del 1-5%. Pero para cálculos pesados (CPU Bound), es del **300% al 500%**.

> [!NOTE]
> **¿Sabía que?**
> El compilador JIT en PHP 8.0 en realidad se ejecuta como parte de OPcache. Si habilita `opcache.jit_buffer_size`, pero olvida establecer `opcache.enable` u `opcache.enable_cli`, ¡el compilador JIT permanecerá completamente deshabilitado sin ninguna advertencia!

---

<a id="modules-changes"></a>
## Cambios en Módulos y el Núcleo

Continúa la limpieza del núcleo de tipos de recursos opacos. Están siendo reemplazados por objetos:

* **cURL**: `curl_init()` devuelve una clase `CurlHandle`.
* **GD**: `imagecreate()` devuelve una instancia de `GdImage`.
* **Sockets**: `socket_create()` devuelve una instancia de `Socket`.

Limpieza de memoria: los nuevos objetos se destruyen **automáticamente** por el Garbage Collector. Funciones como `curl_close()` ya no tienen sentido.

Otros cambios en extensiones:
* **JSON** ahora está integrado permanentemente en el núcleo (no se puede deshabilitar con la bandera `--disable-json`).
* **XML-RPC** se ha movido del núcleo a **PECL**.
* Se ha añadido la función matemática **`fdiv()`**, que permite la división por cero (devuelve `INF`, `-INF` o `NAN` en lugar de un error).

---

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

Incluso si no usa la nueva sintaxis, actualizar a PHP 8.0 puede romper aplicaciones debido a comportamientos de tiempo de ejecución más estrictos y eliminaciones de funciones. A continuación tiene una lista de verificación compacta de los fallos más comunes.

### Lenguaje / palabras clave / eliminaciones

* `match` es ahora una **palabra clave reservada**.
* `mixed` es ahora una **palabra reservada** (no se puede usar para nombres de clases/interfaces/traits y está prohibido en espacios de nombres).
* `__autoload()` se ha eliminado. Use `spl_autoload_register()` en su lugar.
* `create_function()` se ha eliminado. Use funciones anónimas / clausuras.
* `each()` se ha eliminado. Use `foreach` o `ArrayIterator`.
* Se han eliminado las definiciones de constantes insensibles a mayúsculas y minúsculas (`define('FOO', 'bar', true)` ya no es compatible).
* Los métodos con el mismo nombre que la clase ya no se tratan como constructores (use `__construct()`).
* Ya no se permite llamar estáticamente a métodos no estáticos.
* Se han eliminado las conversiones `(real)` y `(unset)`.

### Manejo de errores y diagnósticos (tiempo de ejecución más estricto)

* Los fallos de aserción ahora lanzan excepciones por defecto.
* Se eliminó la directiva INI `track_errors` (por lo que `$php_errormsg` ya no está disponible; use `error_get_last()` en su lugar).
* El operador `@` ya no silencia los errores fatales.
* El nivel predeterminado de reporte de errores ahora es `E_ALL`.
* Muchas advertencias se convirtieron en excepciones `Error` (por ejemplo: escribir en una propiedad de un elemento no-objeto, tipos de claves de array o desplazamientos de cadena no válidos, desempaquetar algo no-array/non-Traversable).
* Muchos avisos (notices) se convirtieron en advertencias (variables/propiedades/claves de array no definidas, conversión de array a cadena, etc.).

### Cadenas numéricas y conversión de tipos

* Las comparaciones no estrictas entre números y cadenas no numéricas cambiaron (los números se comparan como cadenas).
* Las operaciones aritméticas/de bits en cadenas no numéricas ahora lanzan `TypeError`.
* La conversión de float a string ahora es independiente de la configuración regional (locale-independent).

---

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

Aquí tiene algunos errores típicos al usar las características de PHP 8.0:

### 1. Expresión Match sin Default
Una expresión `match` debe ser exhaustiva. Si ninguna de las condiciones coincide y no hay una rama `default`, PHP lanzará un `UnhandledMatchError`.

```php
// app/Services/PaymentService.php
// MALO: Fallará si $status es 'failed'
$message = match ($status) {
    'pending' => 'Esperando pago',
    'completed' => 'Pagado con éxito',
};

// BUENO: Siempre maneje el caso alternativo
$message = match ($status) {
    'pending' => 'Esperando pago',
    'completed' => 'Pagado con éxito',
    default => 'El pago falló o fue cancelado',
};
```

### 2. Efectos secundarios en la cadena del operador Nullsafe
El operador nullsafe corta la ejecución de toda la cadena si cualquier enlace resulta en `null`. Sin embargo, si intenta asignar un valor en una cadena nullsafe o llamar a un método que tiene efectos secundarios, es posible que no se ejecute. Además, el operador nullsafe no se puede usar en el lado izquierdo de una asignación.

```php
// app/Services/UserService.php
// MALO: Error de sintaxis - no se puede escribir en una cadena nullsafe
$session?->user?->name = 'John'; 

// MALO: Es posible que el método con efectos secundarios no se llame si $logger es nulo
$logger?->log('Action occurred')?->sendNotification();
```

### 3. Posición de los Argumentos Nombrados
Si bien puede mezclar argumentos posicionales y nombrados, los nombrados siempre deben ir **después** de los posicionales. Mezclarlos en el orden incorrecto resulta en un error de compilación.

```php
// app/Services/NotificationService.php
// MALO: Argumento nombrado antes que argumento posicional (Error de compilación)
sendEmail(subject: 'Hello', $userEmail);

// BUENO: Argumentos nombrados al final
sendEmail($userEmail, subject: 'Hello');
```

---

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

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

### Pregunta 1: ¿Cuál es el resultado del siguiente código?
```php
// app/Http/Controllers/TestController.php
$result = 0 == 'hello';
var_dump($result);
```
* A) `bool(true)`
* B) `bool(false)`
* C) `TypeError`

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

**Respuesta: B**  
En PHP 8.0, la comparación no estricta (`==`) entre un número y una cadena no numérica los compara como cadenas de texto. Por lo tanto, `0 == 'hello'` se evalúa como `'0' == 'hello'`, lo cual es `false`. En PHP 7.4, habría devuelto `true`.
</details>

### Pregunta 2: ¿Qué excepción se lanza si una expresión `match` no tiene ninguna rama coincidente ni una rama `default`?
* A) `RuntimeException`
* B) `ValueError`
* C) `UnhandledMatchError`

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

**Respuesta: C**  
PHP 8.0 lanza un `UnhandledMatchError` si ninguna de las ramas coincide con el sujeto y no se proporciona una rama `default`.
</details>

---

## Resumen (Conclusión)

Trate a PHP 8.0 como un **reinicio de la plataforma**: menos sorpresas de comparaciones flexibles, fallos más claros de las APIs internas y un sistema de tipos que finalmente coincide con la forma en que se escriben las grandes bases de código de PHP. La recompensa es menos errores exclusivos de producción y una ruta más fluida a 8.1+.