
# Waardeconversie

Elke aanroep die de grens tussen PHP en JS passeert — `setGlobal`, `call` of een hostfunctie die wordt aangeroepen vanuit JS — gaat via een converter:

- **PHP → JS** wanneer waarden van de host naar de engine stromen (`setGlobal()`, argumenten van `call()`, retourwaarden van hostfuncties).
- **JS → PHP** wanneer waarden van JS terug naar buiten stromen. Er zijn twee paden met enigszins afwijkende regels: resultaten van `eval()` en `call()`, en de argumenten die JS aan een hostfunctie doorgeeft.

Conversie maakt kopieën. Primitieven worden omgezet naar primitieven, arrays en objecten worden gekopieerd naar nieuwe structuren, en callables van PHP worden verpakt als functies van JS. Behalve de callables zelf wordt er niets gedeeld via een referentie.

## PHP → JS

| Waarde van PHP | Resultaat van JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 double; ints boven 2^53 verliezen precisie) |
| `float` | `number` (NaN, ±Infinity, ±0 blijven behouden) |
| `string` | `string` (UTF-8-bytes van PHP geïnterpreteerd als UTF-16-code-eenheden), ook wanneer dit de naam is van een functie van PHP |
| lijst `array` (sleutels 0, 1, 2, … in volgorde) | nieuwe `Array`, elementen recursief geconverteerd |
| elke andere `array` | nieuw gewoon `Object`, waarden recursief geconverteerd |
| `Closure`, aanroepbaar object, callable array zoals `[$object, 'method']` | `function` die de callable van PHP aanroept |
| elk ander object, `Generator`, resource | `undefined` |

Arrays met gemengde sleutels (PHPs `[0 => 'a', 'name' => 'b']`) worden gewone objecten, omdat arrays in JS een aaneengesloten ruimte voor gehele indexen moeten hebben.

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

Omdat arrays worden gekopieerd, blijven wijzigingen van JS binnen JS:

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

Objecten van PHP die niet callable zijn, kunnen niet worden geconverteerd; ze komen aan als `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Zet ze eerst om naar arrays, bijvoorbeeld met `get_object_vars()`, of maak hun methoden beschikbaar als closures. Zie [Gedeelde objecten](https://phasis.inline0.dev/nl/docs/interop/shared-objects).

## JS → PHP: resultaten van `eval()` en `call()`

| Waarde van JS | Resultaat van PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (integraal, binnen int-bereik) | `int` (`-0` wordt `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (overig) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | lijst `array`, elementen recursief geconverteerd |
| `TypedArray` | lijst `array` met getallen |
| elk ander object (gewone objecten, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | associatieve `array` met de eigen data-eigenschappen, waarbij accessors en eigenschappen met functiewaarden worden overgeslagen |

De laatste rij betekent dat objecten waarvan de status in interne slots is opgeslagen, worden omgezet naar een lege array: `new Date(0)`, `new Map([["a", 1]])` en `Promise.resolve(5)` retourneren allemaal `[]`. Een `Error` retourneert zijn eigen `message` en `stack`. De interne verwijzingen van een object dat naar zichzelf verwijst, worden omgezet naar `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)');              // []
```

Converteer de waarde binnen JS wanneer je een van deze waarden nodig hebt 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: argumenten van hostfuncties

De argumenten die JS aan een hostfunctie van PHP doorgeeft, volgen dezelfde regels voor primitieven, arrays en gewone objecten, met deze verschillen:

| Waarde van JS | Argument van PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | het eigen object van de engine van `JsValue`, niet geconverteerd |
| object | associatieve `array` met alle eigen eigenschappen, waaronder accessors (waarvan getters worden uitgevoerd) en eigenschappen met functiewaarden |
| pijlfunctie | een `array` (meestal lege) |
| `function` gedeclareerd met het sleutelwoord `function`, of elk object dat naar zichzelf verwijst | geen conversie: de kopie wordt nooit voltooid en PHP raakt door zijn geheugen heen ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Er is nog geen manier om een callback van JS te ontvangen in een hostfunctie. Geef gegevens door, geen functies.

## Getallen en precisie

Getallen van JavaScript zijn doubles volgens IEEE 754. Gehele getallen van PHP zijn systeemeigen (64-bit in elke moderne build). De conversie behoudt gehele getallen tot ±2^53 exact; grotere gehele getallen van PHP worden omgezet naar `number` en verliezen precisie voorbij 16 decimale cijfers. Gebruik `BigInt` binnen JS als je exacte rekenkundige bewerkingen nodig hebt op grotere gehele getallen, en geef het resultaat door als tekenreeks.

## Tekenreeksen

Tekenreeksen van PHP zijn bytearrays; tekenreeksen van JS zijn reeksen UTF-16-code-eenheden. De engine overbrugt dit door de bytes van PHP als UTF-8 te interpreteren en ze intern opnieuw naar UTF-16 te coderen. Dit verloopt zonder verlies voor geldige UTF-8-invoer. Bytes die geen geldige UTF-8 vormen, worden ongewijzigd doorgegeven in plaats van vervangen door U+FFFD.

`String.length`, geïndexeerde toegang (`s[0]`) en slicing tellen allemaal UTF-16-code-eenheden volgens de ECMAScript-specificatie — geen codepoints en geen bytes. Tekenreeksen met aanvullende tekens zoals `'🍕'` hebben daarom `.length === 2` (de twee surrogaathelften van U+1F355).

## Stabiliteit van de retourconversie

Gewone gegevens behouden hun structuur bij een retourconversie: een waarde van PHP met scalaren, lijsten en associatieve arrays die via `setGlobal()` wordt doorgegeven en via `eval()` of `call()` terugkomt, heeft dezelfde structuur. Een uitzondering zijn integrale floats, die terugkomen als `int` (`1.0` retourneert `1`).

Al het overige in de bovenstaande tabellen dat wordt omgezet naar `null`, `undefined` of `[]` behoudt de oorspronkelijke waarde niet bij een retourconversie.
