
# Værdikonvertering

Hvert kald, der krydser grænsen mellem PHP og JS — `setGlobal`, `call`, en værtsfunktion kaldt fra JS — går gennem en konverter:

- **PHP → JS**, når værtsværdier føres ind i motoren (`setGlobal()`, `call()`-argumenter, returværdier fra værtsfunktioner).
- **JS → PHP**, når JS-værdier føres ud igen. Der er to veje med lidt forskellige regler: resultater fra `eval()` og `call()` samt de argumenter, JS sender til en værtsfunktion.

Konvertering kopierer. Primitive værdier mappes til primitive værdier, arrays og objekter kopieres til nye strukturer, og PHP-callables indpakkes som JS-funktioner. Intet deles som reference bortset fra selve de callable objekter.

## PHP → JS

| PHP-værdi | JS-resultat |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 double; heltal over 2^53 mister præcision) |
| `float` | `number` (NaN, ±Infinity, ±0 bevares) |
| `string` | `string` (UTF-8-PHP-bytes fortolket som UTF-16-kodeenheder), også når den navngiver en PHP-funktion |
| liste `array` (nøglerne 0, 1, 2, … i rækkefølge) | nyt `Array`, elementer konverteres rekursivt |
| ethvert andet `array` | nyt almindeligt `Object`, værdier konverteres rekursivt |
| `Closure`, objekt, der kan kaldes, callable array som `[$object, 'method']` | `function`, der kalder den callable PHP |
| ethvert andet objekt, `Generator`, ressource | `undefined` |

Arrays med blandede nøgler (PHPs `[0 => 'a', 'name' => 'b']`) bliver til almindelige objekter, fordi arrays i JS skal have sammenhængende heltalsindekser.

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

Da arrays kopieres, forbliver ændringer i JS i JS:

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

PHP-objekter, der ikke kan kaldes, konverteres ikke; de ankommer som `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Konvertér dem først til arrays, f.eks. med `get_object_vars()`, eller eksponér deres metoder som closure-funktioner. Se [Delte objekter](https://phasis.inline0.dev/da/docs/interop/shared-objects).

## JS → PHP: `eval()`- og `call()`-resultater

| JS-værdi | PHP-resultat |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (heltal inden for int-området) | `int` (`-0` bliver til `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (andet) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | liste `array`, elementer konverteres rekursivt |
| `TypedArray` | liste `array` med tal |
| ethvert andet objekt (almindelige objekter, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | associativt `array` med dets egne dataegenskaber; accessorer og egenskaber med funktionsværdier springes over |

Den sidste række betyder, at objekter, hvis tilstand ligger i interne slots, konverteres til et tomt array: `new Date(0)`, `new Map([["a", 1]])` og `Promise.resolve(5)` returnerer alle `[]`. Et `Error` returnerer sit eget `message` og `stack`. Et objekt, der refererer til sig selv, konverterer sine indre referencer til `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)');              // []
```

Konvertér dem inde i JS, når du har brug for en af disse værdier i 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: argumenter til værtsfunktioner

Argumenter, som JS sender til en PHP-værtsfunktion, følger de samme regler for primitive værdier, arrays og almindelige objekter med disse forskelle:

| JS-værdi | PHP-argument |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | motorens eget `JsValue`-objekt, ikke konverteret |
| objekt | associativt `array` med alle dets egne egenskaber, herunder accessorer (hvis getters køres) og egenskaber med funktionsværdier |
| arrow-funktion | et (normalt tomt) `array` |
| `function` erklæret med nøgleordet `function` eller ethvert objekt, der refererer til sig selv | ingen konvertering: kopien afsluttes aldrig, og PHP løber tør for hukommelse ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Det er endnu ikke muligt at modtage et JS-callback i en værtsfunktion. Send data, ikke funktioner, over denne grænse.

## Tal og præcision

JavaScript-tal er IEEE 754-doubles. PHP-heltal er platformens native type (64-bit i alle moderne builds). Konverteringen bevarer heltal op til ±2^53 nøjagtigt; større PHP-heltal bliver til `number` og mister præcision efter 16 decimalcifre. Brug `BigInt` inde i JS, hvis du har brug for præcis aritmetik med større heltal, og send resultatet ud som en streng.

## Strenge

PHP-strenge er byte-arrays; JS-strenge er sekvenser af UTF-16-kodeenheder. Motoren forbinder dem ved at fortolke PHP-bytes som UTF-8 og omkode dem til UTF-16 internt. Dette er tabsfrit for gyldigt UTF-8-input. Bytes, der ikke er gyldig UTF-8, sendes videre uændret i stedet for at blive erstattet med U+FFFD.

`String.length`, indekseret adgang (`s[0]`) og udsnit tæller alle UTF-16-kodeenheder i henhold til ECMAScript-specifikationen — ikke kodepunkter og ikke bytes. Strenge med astrale tegn som `'🍕'` har derfor `.length === 2` (de to surrogatdele af U+1F355).

## Stabilitet ved tur-retur-konvertering

Almindelige data overlever en tur-retur-konvertering: en PHP-værdi bestående af skalære værdier, lister og associative arrays, som sendes ind via `setGlobal()` og kommer tilbage via `eval()` eller `call()`, har samme struktur, bortset fra at heltalsformaterede floats kommer tilbage som `int` (`1.0` returnerer `1`).

Alt andet i tabellerne ovenfor, der mappes til `null`, `undefined` eller `[]`, overlever ikke en tur-retur-konvertering.
