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()ycall(), 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.
$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:
$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.
$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:
$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.