Conversão de valores
Toda chamada que cruza a fronteira PHP↔JS — setGlobal, call ou uma função hospedeira chamada por JS — passa por um conversor:
- PHP → JS quando valores hospedeiros entram no mecanismo (
setGlobal(), argumentos decall()e valores de retorno de funções hospedeiras). - JS → PHP quando valores JS voltam para fora. Há dois caminhos com regras um pouco diferentes: resultados de
eval()ecall()e os argumentos que JS passa para uma função hospedeira.
A conversão faz cópias. Valores primitivos são mapeados para valores primitivos; arrays e objetos são copiados para novas estruturas, e callables PHP são envolvidos em funções JS. Nada é compartilhado por referência, exceto os próprios callables.
PHP → JS
| Valor PHP | Resultado JS |
|---|---|
null |
null |
true / false |
true / false |
int |
number (double IEEE 754; inteiros acima de 2^53 perdem precisão) |
float |
number (NaN, ±Infinity e ±0 são preservados) |
string |
string (bytes UTF-8 PHP interpretados como unidades de código UTF-16), mesmo quando nomeia uma função PHP |
lista array (chaves 0, 1, 2, … em ordem) |
novo Array; elementos convertidos recursivamente |
qualquer outro array |
novo Object simples; valores convertidos recursivamente |
Closure, objeto invocável, array callable como [$object, 'method'] |
function que chama o callable PHP |
qualquer outro objeto, Generator, recurso |
undefined |
Arrays com chaves mistas (o PHP [0 => 'a', 'name' => 'b']) tornam-se objetos simples, pois arrays em JS precisam ter índices inteiros contíguos.
$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 os arrays são copiados, as alterações feitas por JS permanecem em JS:
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list); // 3
$engine->eval('list.length'); // 4
Objetos PHP que não são callable não são convertidos; eles chegam como undefined (problema #18). Primeiro, converta-os em arrays, por exemplo, com get_object_vars(), ou exponha seus métodos como closures. Consulte Objetos compartilhados.
JS → PHP: resultados de eval() e call()
| Valor JS | Resultado PHP |
|---|---|
undefined, null |
null |
boolean |
bool |
number (inteiro, dentro do intervalo de int) |
int (-0 torna-se 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (outro) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
lista array; elementos convertidos recursivamente |
TypedArray |
lista array de números |
qualquer outro objeto (objetos simples, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
array associativo de suas próprias propriedades de dados, ignorando accessors e propriedades cujos valores são funções |
A última linha significa que objetos cujo estado fica em slots internos são convertidos em um array vazio: new Date(0), new Map([["a", 1]]) e Promise.resolve(5) retornam []. Um Error retorna seu próprio message e stack. Um objeto que referencia a si mesmo converte suas referências internas em 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)'); // []
Converta dentro de JS quando precisar de um desses valores em 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 funções hospedeiras
Os argumentos que JS passa para uma função hospedeira PHP seguem as mesmas regras para valores primitivos, arrays e objetos simples, com estas diferenças:
| Valor JS | Argumento PHP |
|---|---|
TypedArray, ArrayBuffer, DataView |
o próprio objeto JsValue do mecanismo, sem conversão |
| objeto | array associativo de todas as suas propriedades, incluindo accessors (cujos getters são executados) e propriedades cujos valores são funções |
| função arrow | um array (geralmente vazio) |
function declarado com a palavra-chave function, ou qualquer objeto que faça referência a si mesmo |
sem conversão: a cópia nunca termina e PHP fica sem memória (problema #21) |
Ainda não há como receber um callback JS em uma função hospedeira. Passe dados, não funções, por essa fronteira.
Números e precisão
Os números JavaScript são doubles IEEE 754. Os inteiros PHP são nativos da plataforma (64 bits em todas as compilações modernas). A conversão preserva exatamente inteiros até ±2^53; inteiros PHP maiores tornam-se number e perdem precisão além de 16 dígitos decimais. Use BigInt dentro de JS se precisar de cálculos exatos com inteiros maiores do que isso e passe o resultado como string.
Strings
Strings PHP são arrays de bytes; strings JS são sequências de unidades de código UTF-16. O mecanismo faz a ponte interpretando bytes PHP como UTF-8 e recodificando-os internamente em UTF-16. Isso não causa perda para entradas UTF-8 válidas. Bytes que não são UTF-8 válido são transmitidos sem alteração, em vez de serem substituídos por U+FFFD.
String.length, o acesso indexado (s[0]) e o fatiamento contam unidades de código UTF-16, de acordo com a especificação ECMAScript — não codepoints nem bytes. Por isso, strings com caracteres fora do plano multilíngue básico, como '🍕', têm .length === 2 (as duas metades substitutas de U+1F355).
Estabilidade de ida e volta
Dados simples sobrevivem à conversão de ida e volta: um valor PHP composto por valores primitivos, listas e arrays associativos, que entra por setGlobal() e retorna por eval() ou call(), produz a mesma estrutura, exceto que floats integrais retornam como int (1.0 retorna 1).
Todo o restante das tabelas acima que é mapeado para null, undefined ou [] não sobrevive à conversão de ida e volta.