
# Conversión de valores

Toda llamada que cruza el límite PHP↔JS — `setGlobal`, `call`, una función del host invocada desde JS — pasa por un conversor:

- **PHP → JS** cuando los valores del host entran en el motor (argumentos de `setGlobal()`, `call()` y valores devueltos por funciones del host).
- **JS → PHP** cuando los valores de JS salen. Hay dos rutas con reglas ligeramente distintas: los resultados de `eval()` y `call()`, y los argumentos que JS pasa a una función del host.

La conversión copia los valores. Los primitivos se convierten en primitivos, y los arrays y objetos se copian en nuevas estructuras; las funciones invocables de PHP se envuelven como funciones de JS. Nada se comparte por referencia, salvo las propias funciones invocables.

## PHP → JS

| Valor de PHP | Resultado de JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (doble IEEE 754; los enteros mayores que 2^53 pierden precisión) |
| `float` | `number` (se conservan NaN, ±Infinity y ±0) |
| `string` | `string` (los bytes PHP UTF-8 se interpretan como unidades de código UTF-16), incluso si nombra una función de PHP |
| lista `array` (claves 0, 1, 2, … en orden) | nuevo `Array`, elementos convertidos recursivamente |
| cualquier otro `array` | nuevo `Object` simple, valores convertidos recursivamente |
| `Closure`, objeto invocable, array invocable como `[$object, 'method']` | `function` que llama a la función invocable de PHP |
| cualquier otro objeto, `Generator`, recurso | `undefined` |

Los arrays con claves mixtas (el `[0 => 'a', 'name' => 'b']` de PHP) se convierten en objetos simples porque los arrays de JS deben tener un espacio de índices enteros contiguo.

```php
$engine->setGlobal('list', [1, 2, 3]);                 // → JS Array
$engine->setGlobal('map',  ['a' => 1, 'b' => 2]);      // → JS Object
$engine->setGlobal('mixed', [0 => 'x', 'k' => 'v']);   // → JS Object
```

Como los arrays se copian, los cambios en JS permanecen en JS:

```php
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4
```

Los objetos de PHP que no son invocables no se convierten; llegan como `undefined` ([incidencia #18](https://github.com/inline0/phasis/issues/18)). Conviértalos primero en arrays, por ejemplo con `get_object_vars()`, o exponga sus métodos como cierres. Consulte [Objetos compartidos](https://phasis.inline0.dev/es/docs/interop/shared-objects).

## JS → PHP: resultados de `eval()` y `call()`

| Valor de JS | Resultado de PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (integral, dentro del rango de enteros) | `int` (`-0` se convierte en `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (otro) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | lista `array`, elementos convertidos recursivamente |
| `TypedArray` | lista `array` de números |
| cualquier otro objeto (objetos simples, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | `array` asociativo de sus propias propiedades de datos, omitiendo accesores y propiedades con valores de función |

La última fila indica que los objetos cuyo estado reside en ranuras internas se convierten en un array vacío: `new Date(0)`, `new Map([["a", 1]])` y `Promise.resolve(5)` devuelven `[]`. Un `Error` devuelve su propio `message` y `stack`. Un objeto que se referencia a sí mismo convierte sus referencias internas en `null`.

```php
$engine->eval('[1, 2, 3]');                // [1, 2, 3]
$engine->eval('({a: 1, b: 2})');           // ['a' => 1, 'b' => 2]
$engine->eval('new Uint8Array([1, 2, 3])'); // [1, 2, 3]
$engine->eval('1234567890123456789n');     // null
$engine->eval('new Date(0)');              // []
```

Convierta dentro de JS cuando necesite uno de esos valores en PHP:

```php
$engine->eval('(1234567890123456789n).toString()');  // "1234567890123456789"
$engine->eval('new Date(0).toISOString()');           // "1970-01-01T00:00:00.000Z"
$engine->eval('Object.fromEntries(new Map([["a", 1]]))'); // ['a' => 1]
```

## JS → PHP: argumentos de funciones del host

Los argumentos que JS pasa a una función del host de PHP siguen las mismas reglas para primitivos, arrays y objetos simples, con estas diferencias:

| Valor de JS | Argumento de PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | el propio objeto `JsValue` del motor, sin convertir |
| objeto | `array` asociativo de todas sus propiedades propias, incluidos los accesores (cuyos getters se ejecutan) y las propiedades con valores de función |
| función flecha | un `array` (normalmente vacío) |
| `function` declarado con la palabra clave `function`, o cualquier objeto que se refiera a sí mismo | sin conversión: la copia nunca termina y PHP se queda sin memoria ([incidencia #21](https://github.com/inline0/phasis/issues/21)) |

Aún no hay forma de recibir una función de retorno de llamada de JS en una función del host. Pase datos, no funciones, a través de este límite.

## Números y precisión

Los números de JavaScript son dobles IEEE 754. Los enteros de PHP son nativos de la plataforma (de 64 bits en todas las compilaciones modernas). La conversión conserva exactamente los enteros hasta ±2^53; los enteros de PHP mayores se convierten en `number` y pierden precisión después de 16 dígitos decimales. Use `BigInt` dentro de JS si necesita aritmética exacta con enteros mayores, y devuelva el resultado como una cadena.

## Cadenas

Las cadenas de PHP son arrays de bytes; las cadenas de JS son secuencias de unidades de código UTF-16. El motor conecta ambos formatos interpretando los bytes de PHP como UTF-8 y recodificándolos internamente en UTF-16. Esto no pierde información si la entrada es UTF-8 válida. Los bytes que no sean UTF-8 válido se pasan sin cambios, en vez de sustituirse por U+FFFD.

`String.length`, el acceso indexado (`s[0]`) y el corte cuentan unidades de código UTF-16 según la especificación de ECMAScript, no puntos de código ni bytes. Por eso, las cadenas con caracteres astrales como `'🍕'` tienen `.length === 2` (las dos mitades sustitutas de U+1F355).

## Estabilidad de ida y vuelta

Los datos simples sobreviven a un recorrido de ida y vuelta: un valor de PHP compuesto por escalares, listas y arrays asociativos que entra por `setGlobal()` y vuelve por `eval()` o `call()` produce la misma estructura, salvo que los números de coma flotante integrales regresan como `int` (`1.0` devuelve `1`).

Todo lo demás de las tablas anteriores que se convierte en `null`, `undefined` o `[]` no sobrevive al recorrido de ida y vuelta.
