
# 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](https://github.com/inline0/phasis/issues/18)). Primeiro, converta-os em arrays, por exemplo, com `get_object_vars()`, ou exponha seus métodos como closures. Consulte [Objetos compartilhados](https://phasis.inline0.dev/pt/docs/interop/shared-objects).

## 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](https://github.com/inline0/phasis/issues/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.
