
# Conversione dei valori

Ogni chiamata che attraversa il confine PHP↔JS — `setGlobal`, `call` o una funzione host invocata da JS — passa attraverso un convertitore:

- **PHP → JS** quando i valori host entrano nel motore (`setGlobal()`, argomenti `call()`, valori restituiti dalle funzioni host).
- **JS → PHP** quando i valori JS escono. Ci sono due percorsi con regole leggermente diverse: i risultati di `eval()` e `call()`, e gli argomenti che JS passa a una funzione host.

La conversione copia i valori. Le primitive restano primitive, array e oggetti vengono copiati in nuove strutture e le funzioni richiamabili PHP sono racchiuse in funzioni JS. Nulla viene condiviso per riferimento, fatta eccezione per le funzioni richiamabili stesse.

## PHP → JS

| Valore PHP | Risultato JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (double IEEE 754; gli interi oltre 2^53 perdono precisione) |
| `float` | `number` (NaN, ±Infinity, ±0 preservati) |
| `string` | `string` (byte UTF-8 PHP interpretati come unità di codice UTF-16), anche quando nomina una funzione PHP |
| `array` elenco (chiavi 0, 1, 2, … in ordine) | nuovo `Array`, elementi convertiti ricorsivamente |
| qualsiasi altro `array` | nuovo `Object` semplice, valori convertiti ricorsivamente |
| `Closure`, oggetto invocabile, array richiamabile come `[$object, 'method']` | `function` che invoca la funzione richiamabile PHP |
| qualsiasi altro oggetto, `Generator`, risorsa | `undefined` |

Gli array con chiavi miste (PHP `[0 => 'a', 'name' => 'b']`) diventano oggetti semplici perché gli array JS devono avere indici interi contigui.

```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
```

Poiché gli array vengono copiati, le modifiche di JS rimangono in JS:

```php
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4
```

Gli oggetti PHP non richiamabili non vengono convertiti: arrivano come `undefined` ([problema n. 18](https://github.com/inline0/phasis/issues/18)). Convertili prima in array, per esempio con `get_object_vars()`, oppure esponi i loro metodi tramite closure. Vedi [Oggetti condivisi](https://phasis.inline0.dev/it/docs/interop/shared-objects).

## JS → PHP: risultati `eval()` e `call()`

| Valore JS | Risultato PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (intero, nell'intervallo int) | `int` (`-0` diventa `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (altro) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | elenco `array`, elementi convertiti ricorsivamente |
| `TypedArray` | elenco `array` di numeri |
| qualsiasi altro oggetto (oggetti semplici, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | `array` associativo delle sue proprietà dati, escluse quelle accessorie e quelle con valori funzione |

L'ultima riga indica che gli oggetti il cui stato risiede in slot interni vengono convertiti in un array vuoto: `new Date(0)`, `new Map([["a", 1]])` e `Promise.resolve(5)` restituiscono tutti `[]`. Un `Error` restituisce il proprio `message` e `stack`. Un oggetto che fa riferimento a sé stesso converte i propri riferimenti interni in `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)');              // []
```

Esegui la conversione all'interno di JS quando ti serve uno di questi valori in 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: argomenti delle funzioni host

Gli argomenti che JS passa a una funzione host PHP seguono le stesse regole per primitive, array e oggetti semplici, con queste differenze:

| Valore JS | Argomento PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | oggetto `JsValue` del motore, non convertito |
| oggetto | `array` associativo di tutte le sue proprietà, incluse quelle accessorie (i cui getter vengono eseguiti) e quelle con valori funzione |
| funzione freccia | un `array` (di solito vuoto) |
| `function` dichiarata con la parola chiave `function`, o qualsiasi oggetto che fa riferimento a sé stesso | nessuna conversione: la copia non termina mai e PHP esaurisce la memoria ([problema n. 21](https://github.com/inline0/phasis/issues/21)) |

Al momento non è possibile ricevere una callback JS in una funzione host. Passa dati, non funzioni, attraverso questo confine.

## Numeri e precisione

I numeri JavaScript sono double IEEE 754. Gli interi PHP sono nativi della piattaforma (a 64 bit in ogni build moderna). La conversione conserva esattamente gli interi fino a ±2^53; gli interi PHP più grandi diventano `number` e perdono precisione oltre le 16 cifre decimali. Usa `BigInt` all'interno di JS se ti servono calcoli esatti con interi più grandi e passa il risultato come stringa.

## Stringhe

Le stringhe PHP sono array di byte; le stringhe JS sono sequenze di unità di codice UTF-16. Il motore le converte interpretando i byte PHP come UTF-8 e ricodificandoli internamente in UTF-16. La conversione non comporta perdite per gli input UTF-8 validi. I byte che non sono UTF-8 valido vengono mantenuti invariati, anziché essere sostituiti con U+FFFD.

`String.length`, l'accesso tramite indice (`s[0]`) e lo slicing contano tutti le unità di codice UTF-16 secondo le specifiche ECMAScript, non i punti di codice né i byte. Le stringhe contenenti caratteri astrali come `'🍕'` hanno quindi `.length === 2` (le due metà surrogate di U+1F355).

## Stabilità del percorso di andata e ritorno

I dati semplici sopravvivono al percorso di andata e ritorno: un valore PHP composto da scalari, elenchi e array associativi, che entra tramite `setGlobal()` e torna tramite `eval()` o `call()`, produce la stessa struttura, salvo che i float interi tornano come `int` (`1.0` restituisce `1`).

Tutti gli altri valori nelle tabelle sopra che vengono convertiti in `null`, `undefined` o `[]` non sopravvivono al percorso di andata e ritorno.
