Pular para o conteúdo

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 de call() 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() e call() 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.

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 os arrays são copiados, as alterações feitas por JS permanecem em JS:

PHP
$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.

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)');              // []

Converta dentro de JS quando precisar de um desses valores em 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 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.