---
title: 'PHP 7.3 from 7.2: Flexible Heredoc, JSON Exceptions, PCRE2 & Migration | DevSense'
description: 'Guía de actualización de PHP 7.3: heredoc/nowdoc flexible, comas finales en llamadas, desestructuración de referencias, is_countable, array_key_first/last, JsonException, Argon2id, PCRE2—y cambios incompatibles importantes.'
faq:
    - { question: '¿Qué es la sintaxis flexible heredoc/nowdoc en PHP 7.3?', answer: 'PHP 7.3 permite indentar la etiqueta de cierre de un heredoc o nowdoc. La indentación de la etiqueta de cierre define la cantidad de indentación que se eliminará de todas las líneas dentro del cuerpo.' }
    - { question: '¿Cómo funciona JsonException en PHP 7.3?', answer: "Al pasar la bandera 'JSON_THROW_ON_ERROR' a 'json_encode()' o 'json_decode()', PHP lanzará una 'JsonException' si la operación falla, en lugar de requerir comprobaciones manuales con 'json_last_error()'." }
    - { question: "¿Qué cambia con la instrucción 'continue' en un 'switch'?", answer: "Usar 'continue' dentro de una sentencia 'switch' ahora emite una advertencia porque se comporta de manera idéntica a 'break'. Para continuar un bucle externo, se debe usar 'continue 2' en su lugar." }
    - { question: '¿Qué funciones auxiliares se introdujeron en PHP 7.3?', answer: "PHP 7.3 agregó 'is_countable()' para verificar si una variable se puede contar, junto con 'array_key_first()' y 'array_key_last()' para recuperar la primera y última clave de un array." }
published: '2026-05-31'
---
# PHP 7.3: Características Principales

¿Alguna vez ha pasado horas depurando un fallo silencioso de análisis JSON en producción porque olvidó verificar `json_last_error()`? PHP 7.3 soluciona este problema de experiencia del desarrollador (DX) al introducir `JsonException` a través de la bandera `JSON_THROW_ON_ERROR`. Representa una versión importante en cuanto a seguridad y comodidad, puliendo la ergonomía sintáctica con los heredocs indentados, las comas finales en las llamadas y la adición de ayudantes esenciales a la biblioteca estándar.

## Índice
* [Heredoc y Nowdoc Flexibles](#flexible-heredoc)
* [Comas finales en llamadas a funciones y métodos](#trailing-commas-calls)
* [Asignaciones de referencias en `list()` y `[]`](#list-references)
* [`instanceof` con literales](#instanceof-literals)
* [`CompileError`](#compile-error)
* [Nuevos y actualizados ayudantes del núcleo](#core-helpers)
* [JSON: `JSON_THROW_ON_ERROR` y `JsonException`](#json-exceptions)
* [Hashing de contraseñas: Argon2id](#argon2id)
* [Extensiones destacadas y stdlib](#extensions-stdlib)
* [Recetas prácticas](#practical-recipes)
* [Errores Comunes](#common-mistakes)
* [Limitaciones](#limitations)
* [Cambios incompatibles con versiones anteriores (notas de migración)](#backward-incompatible)
* [Depreciaciones (corregir pronto)](#deprecations)
* [Otros cambios y operaciones (compilación, INI, rendimiento)](#other-changes)
* [Quiz interactivo](#interactive-quiz)
* [Reflexiones finales](#closing-thoughts)

---

<a id="flexible-heredoc"></a>
## Heredoc y Nowdoc Flexibles

La etiqueta de cierre **ya no está obligada** a situarse en la columna 0 de la antigua forma rígida: ahora puede estar **indentada**, y esa misma indentación se **elimina de cada línea** del cuerpo. Eso hace que el código SQL, HTML y los textos de ayuda CLI incrustados sean mucho más legibles en código compatible con PSR-12.

**Advertencia de migración:** si el cuerpo **contiene el mismo token** que la etiqueta de cierre (ahora posiblemente indentada), PHP puede tratarlo como si estuviera **terminando la cadena antes de tiempo**—elija **etiquetas largas y únicas** (como `SQL_GET_USER` en lugar de `SQL`).

```php
// src/QueryBuilder.php
$query = <<<SQL
    SELECT id, name
    FROM users
    WHERE active = 1
    SQL;
```

> [!NOTE]
> ¿Sabía que? La indentación de la etiqueta de cierre dicta la indentación eliminada del texto. Si indenta el cuerpo *menos* que la etiqueta de cierre, PHP lanzará un ParseError.

---

<a id="trailing-commas-calls"></a>
## Comas finales en llamadas a funciones y métodos

Las llamadas pueden terminar con una coma final—útil para diffs de varias líneas y listas de argumentos generadas:

```php
// src/LoggerService.php
$this->logger->info(
    'User saved',
    [
        'id' => $user->id,
        'ip' => $request->ip(),
    ],
);
```

Esto se aplica a las **llamadas**, no a declarar parámetros `function foo($a,)` en 7.3 (las comas finales en la lista de parámetros llegaron en PHP 8.0).

> [!NOTE]
> ¿Sabía que? Las comas finales en las *declaraciones* de funciones (definición de parámetros) siguen siendo un error de sintaxis en PHP 7.3. Solo se admiten en las *invocaciones* (llamadas).

---

<a id="list-references"></a>
## Asignaciones de referencias en `list()` y `[]`

La desestructuración puede vincular variables **por referencia**:

```php
// src/Destructure.php
[&$head, $middle, &$tail] = $parts;
// misma idea con list():
list(&$a, $b) = $pair;
```

Utilice esto cuando modifique intencionadamente partes de una estructura compartida; de lo contrario, prefiera valores para mantener claro el flujo de datos.

---

<a id="instanceof-literals"></a>
## `instanceof` con literales

El lado izquierdo puede ser un **literal**; el resultado es siempre **`false`**. Esto existe principalmente por **consistencia** y para facilitar el análisis estático, no para la lógica en tiempo de ejecución de la que dependa su código.

```php
// src/Validation.php
$result = (123 instanceof User); // false
```

---

<a id="compile-error"></a>
## `CompileError`

`CompileError` se introduce como base para ciertos **fallos en tiempo de compilación**; `ParseError` lo **extiende**. Inicialmente, esto se muestra de forma más visible cuando **`token_get_all($code, TOKEN_PARSE)`** encuentra problemas de análisis—algunas situaciones que eran fallos fatales ahora pueden surgir como excepciones en las herramientas.

```php
// src/ErrorHandling.php
try {
    eval('invalid code');
} catch (CompileError $e) {
    echo "La compilación falló: " . $e->getMessage();
}
```

---

<a id="core-helpers"></a>
## Nuevos y actualizados ayudantes del núcleo

* **`array_key_first($array)`** / **`array_key_last($array)`** — evite los trucos de obtener claves con `reset()`/`end()`; el comportamiento está definido para su tipo de array (consulte el manual para arrays vacíos).
* **`is_countable($value)`** — `true` para arrays y objetos que implementan `Countable`—reemplaza la comprobación común `is_array($x) || $x instanceof \Countable` antes de usar `count()`.
* **`hrtime($as_number = false)`** — tiempo monótono de alta resolución (nanosegundos); prefiéralo sobre `microtime(true)` para medir **intervalos** (no hora de calendario del sistema).
* **`net_get_interfaces()`** — enumera las interfaces de red donde se admita (dependiente de la plataforma).

```php
// src/Helpers.php
$firstKey = array_key_first($myArray);
if (is_countable($data)) {
    $count = count($data);
}
```

> [!NOTE]
> ¿Sabía que? `hrtime()` utiliza el reloj monótono del sistema, que es inmune a los cambios de hora del sistema (como las sincronizaciones NTP). Esto lo convierte en el único método fiable para medir intervalos de tiempo de ejecución.

---

<a id="json-exceptions"></a>
## JSON: `JSON_THROW_ON_ERROR` y `JsonException`

Pase **`JSON_THROW_ON_ERROR`** a **`json_encode()`** / **`json_decode()`** para obtener una excepción **`JsonException`** en lugar de hacer malabarismos con **`json_last_error()`**. **`JSON_PARTIAL_OUTPUT_ON_ERROR`** sigue prevaleciendo cuando se aplican ambas banderas.

```php
// src/JsonParser.php
try {
    $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    // Manejar el error
}
```

---

<a id="argon2id"></a>
## Hashing de contraseñas: Argon2id

Cuando PHP se compila con una versión suficientemente nueva de **libargon2**, **`password_hash()`** acepta **`PASSWORD_ARGON2ID`**, proporcionando la variante **híbrida i+d** de Argon2 junto con el soporte existente de Argon2i.

```php
// src/Security.php
$hash = password_hash('secret', PASSWORD_ARGON2ID);
```

---

<a id="extensions-stdlib"></a>
## Extensiones destacadas y stdlib

* **PCRE**: motor actualizado a **PCRE2**—ligeros cambios de comportamiento (rangos de clases de caracteres más estrictos, etc.); **`preg_quote()`** ahora también escapa el carácter **`#`**.
* **Multibyte (mbstring)**: **Unicode 11**, cadenas **>2GB**, operaciones de mayúsculas y minúsculas más rápidas; **case-folding completo** para comparaciones insensibles a mayúsculas y minúsculas; capturas con nombre de **`mb_ereg_*`** alineadas más cerca con la ergonomía de PCRE.
* **LDAP**: los **controles** LDAP se integran a través de las API de búsqueda/modificación y **`ldap_parse_result()`**; se mejoró el manejo de opciones para controles de servidor/cliente.
* **MySQLi / PDO MySQL**: las sentencias preparadas pueden mostrar **fracciones de segundo** para columnas de tipo `DATETIME(6)` / `TIMESTAMP(6)`.
* **FPM**: opciones de registro (`log_limit`, `log_buffering`, `decorate_workers_output`); **`getallheaders()`** disponible bajo FPM.
* **Sesiones / cookies**: **`session_set_cookie_params(array $options)`** y **`session.cookie_samesite`**; **`setcookie()` / `setrawcookie()`** aceptan un array `$options` incluyendo **`samesite`**.
* **Stream**: los nombres IPv6 de **`stream_socket_get_name()`** utilizan el formato de host **entre corchetes** (por ejemplo, `[::1]:1337`).
* **SPL autoload**: si un cargador automático **lanza una excepción**, **se omiten los siguientes cargadores automáticos**.
* **IMAP**: los inicios de sesión de **rsh/ssh** están **deshabilitados de forma predeterminada**—actívelos solo con cuidado (`imap.enable_insecure_rsh`).

---

<a id="practical-recipes"></a>
## Recetas prácticas

### Proteger antes de `count()`

```php
// src/recipes.php
if (is_countable($rows)) {
    $n = count($rows);
}
```

### Primeras y últimas claves estables

```php
// src/recipes.php
$firstKey = array_key_first($map);
$lastKey = array_key_last($map);
```

### Análisis estricto de JSON

```php
// src/recipes.php
try {
    $payload = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    // manejar o envolver
}
```

### Medición de tiempo de alta resolución

```php
// src/recipes.php
$t0 = hrtime(true);
// ...
$elapsedNs = hrtime(true) - $t0;
```

---

<a id="common-mistakes"></a>
## ⚠️ Erreurs courantes

Aquí tiene algunos errores típicos cometidos por los desarrolladores en PHP 7.3:

### 1. Indentar las etiquetas de cierre de Heredoc sin querer
Si el cuerpo tiene líneas indentadas *menos* que la etiqueta de cierre, o la etiqueta de cierre tiene espacios finales, se lanza un ParseError.
```php
// src/BadHeredoc.php
// Malo: El cuerpo está indentado menos que la etiqueta de cierre, lanzando ParseError
$text = <<<TEXT
Line 1
    Line 2
      TEXT;
```
```php
// src/GoodHeredoc.php
// Bueno: La indentación de la etiqueta de cierre coincide con la indentación mínima del cuerpo
$text = <<<TEXT
    Line 1
    Line 2
    TEXT;
```

### 2. Usar `continue` dentro de un `switch`
`continue` dentro de sentencias switch actúa como `break` en PHP, lo que a menudo resulta en bucles infinitos cuando los desarrolladores esperan que proceda a la siguiente iteración del bucle. PHP 7.3 emite una advertencia por esto.
```php
// src/BadContinueSwitch.php
// Malo: Emite la advertencia: \"continue\" targeting \"switch\" is equivalent to \"break\"
foreach ($items as $item) {
    switch ($item) {
        case 'skip':
            continue;
    }
}
```
```php
// src/GoodContinueSwitch.php
// Bueno: Use \"continue 2\" para dirigirse al bucle foreach externo
foreach ($items as $item) {
    switch ($item) {
        case 'skip':
            continue 2;
    }
}
```

---

<a id="limitations"></a>
## Limitaciones

* **Comas finales**: Solo se admiten en llamadas a funciones y métodos. Intentar escribir `function foo($a, $b,) {}` (coma final en la declaración) lanzará un ParseError de sintaxis.
* **Heredoc flexible**: La etiqueta de cierre debe estar en su propia línea y no puede tener ningún texto (excepto un punto y coma o una nueva línea) después de ella. Cualquier espacio en blanco después del identificador de cierre provocará un error de compilación.

---

<a id="backward-incompatible"></a>
## Cambios incompatibles con versiones anteriores (notas de migración)

### Núcleo

* **Heredoc/nowdoc flexible**: las cadenas que **incrustan la etiqueta de cierre** pueden cambiar de significado o convertirse en **errores de análisis**—renombre las etiquetas o escape el contenido.
* **`continue` dentro de `switch`**: ahora emite una **advertencia** (en PHP se comportaba como `break`). **Audite `switch` + `continue`.**
* **`ArrayAccess` con claves de cadena numéricas**: para claves literales **`\"123\"`**, PHP **deja de forzar** a entero para `ArrayAccess`—se llama a **`offsetGet(\"123\")`** en lugar de **`offsetGet(123)`**.
* **Propiedades estáticas mediante trucos de referencias**: ya no puede **separar** propiedades estáticas heredadas mediante asignación por referencia.
* **Referencias de accesos a arrays o propiedades**: las referencias devuelvas de lecturas encadenadas de **arrays o propiedades** se **desenvuelven inmediatamente**.
* **Desempaquetado de `...$traversable`**: las **claves que no son enteros** en `Traversable` **ya no se desempaquetan** para llamadas.
* **Advertencias promovidas a excepciones (`EH_THROW`)**: dichas excepciones **ya no rellenan** **`error_get_last()`**.
* **Mensajes de `TypeError`**: los tipos incorrectos se muestran como **`int` / `bool`**, no como **`integer` / `boolean`**.
* **`compact()`**: las **variables no definidas** pasadas por nombre producen un **Notice**.
* **`getimagesize()`** (y similares): el tipo MIME de BMP se reporta como **`image/bmp`** (IANA), no como **`image/x-ms-bmp`**.
* **Cookies (desde PHP 7.3.23)**: los **nombres** de las cookies entrantes **no se decodifican por URL** (alineación de seguridad).

### BCMath

* Las advertencias se dirigen a través del **controlador de errores estándar** de PHP.
* **`bcmul()` / `bcpow()`** respetan la **escala solicitada** de manera más estricta.

### IMAP

* Tunelización **rsh/ssh** **desactivada** a menos que habilite explícitamente **`imap.enable_insecure_rsh`**.

### Regex Multibyte (`mb_ereg_*`)

* Las **capturas con nombre** cambian la **forma del array de coincidencias** y la sintaxis de sustitución de **`mb_ereg_replace()`**.

### MySQLi y PDO MySQL

* Las **fracciones de segundo** ahora son visibles en las rutas de resultados vinculados.

### Reflection

* Las exportaciones de cadenas utilizan las formas de tipo **`int` / `bool`** en la salida.

### SimpleXML

* Las operaciones numéricas en nodos de texto SimpleXML eligen **int frente a float** en lugar de forzar siempre a través de **int**.

---

<a id="deprecations"></a>
## Depreciaciones (corregir pronto)

Elementos principales de alta señal:

* **`define()` insensible a mayúsculas y minúsculas** (`true` como tercer argumento) y el uso de una **capitalización diferente** a la de la definición.
* **Función en espacio de nombres llamada `assert()`**—evítela; el motor trata **`assert()`** de manera especial.
* **Needles que no son cadenas** en la **familia `strpos`**—convierta explícitamente.
* **`fgetss()`**, **`gzgetss()`**, **`SplFileObject::fgetss()`** y el filtro **`string.strip_tags`**.
* **`FILTER_FLAG_SCHEME_REQUIRED` / `FILTER_FLAG_HOST_REQUIRED`** con **`FILTER_VALIDATE_URL`** (redundante).
* **`image2wbmp()`** (GD).
* **`Normalizer::NONE`** con ICU ≥ 56.
* **Alias `mbereg_*()`** no documentados—cambie a **`mb_ereg_*()`**.
* Depreciación de la directiva INI **`pdo_odbc.db2_instance_name`**.

---

<a id="interactive-quiz"></a>
## Quiz interactivo

Ponga a prueba sus conocimientos sobre PHP 7.3:

1. **¿Qué afirmación es correcta con respecto a las comas finales en PHP 7.3?**
   * A) Se permiten en llamadas a funciones pero no en declaraciones de funciones.
   * B) Se permiten en declaraciones de funciones pero no en llamadas a funciones.
   * C) Se permiten tanto en declaraciones como en llamadas.
   * D) Siguen siendo errores de sintaxis en todas partes.

   <details>
   <summary>Haga clic para ver la respuesta</summary>
   
   **Respuesta correcta: A**  
   PHP 7.3 permite comas finales en llamadas a funciones/métodos (invocaciones), pero declarar una función con una coma final en la lista de parámetros sigue siendo un error de análisis (parse error).
   </details>

2. **¿Qué hace PHP 7.3 al encontrar un `continue` dentro de una sentencia `switch`?**
   * A) Se comporta como un `continue 2` en el bucle padre de forma silenciosa.
   * B) Lanza un ParseError fatal.
   * C) Emite una advertencia indicando que 'continue' dirigido a un 'switch' equivale a 'break'.
   * D) Continúa la ejecución normalmente sin advertencias.

   <details>
   <summary>Haga clic para ver la respuesta</summary>
   
   **Respuesta correcta: C**  
   PHP 7.3 emite una advertencia para esto porque `continue` dentro de un `switch` no salta iteraciones de un bucle externo; se comporta como `break`.
   </details>

3. **¿Qué función se introdujo en PHP 7.3 para verificar si una variable se puede pasar a `count()`?**
   * A) `is_iterable()`
   * B) `is_countable()`
   * C) `can_count()`
   * D) `is_array_or_countable()`

   <details>
   <summary>Haga clic para ver la respuesta</summary>
   
   **Respuesta correcta: B**  
   `is_countable()` devuelve `true` si la variable es un array o una instancia de una clase que implementa `Countable`.
   </details>

---

<a id="closing-thoughts"></a>
## Reflexiones finales

PHP 7.3 recompensa una **revisión enfocada de diferencias (diffs)**: busque **`continue` + `switch`**, **`compact(`**, implementaciones de **`ArrayAccess`**, etiquetas **heredoc** y **desempaquetado de argumentos `...$generator`**. Vuelva a ejecutar las pruebas de integración de **regex** y **fecha/hora**, luego avance—**7.4** agregará características de lenguaje mucho más grandes, por lo que limpiar las depreciaciones de 7.3 temprano reduce el ruido en el próximo salto.