Saltar al contenido

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). Conviértalos primero en arrays, por ejemplo con get_object_vars(), o exponga sus métodos como cierres. Consulte Objetos compartidos.

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)

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.