
# Värdekonvertering

Varje anrop som passerar gränsen PHP↔JS — `setGlobal`, `call`, en värdfunktion som anropas från JS — går genom en konverterare:

- **PHP → JS** när värden från värden flödar in i motorn (`setGlobal()`, argument till `call()`, returvärden från värdfunktioner).
- **JS → PHP** när värden i JS flödar tillbaka. Det finns två vägar med något olika regler: resultat från `eval()` och `call()`, samt argumenten som JS skickar till en värdfunktion.

Konverteringen kopierar. Primitiva värden mappas till primitiva värden, arrayer och objekt kopieras till nya strukturer, och anropsbara värden i PHP omsluts som funktioner i JS. Inget delas genom referenser förutom själva de anropsbara värdena.

## PHP → JS

| Värde i PHP | Resultat i JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (IEEE 754 double; heltal större än 2^53 förlorar precision) |
| `float` | `number` (NaN, ±Infinity, ±0 bevaras) |
| `string` | `string` (UTF-8 PHP-byte tolkade som UTF-16-kodenheter), även när det namnger en funktion i PHP |
| listan `array` (nycklarna 0, 1, 2, … i ordning) | ny `Array`, element konverteras rekursivt |
| alla andra `array` | nytt vanligt `Object`, värden konverteras rekursivt |
| `Closure`, anropsbart objekt, anropsbar array som `[$object, 'method']` | `function` som anropar den anropsbara funktionen i PHP |
| alla andra objekt, `Generator`, resurs | `undefined` |

Arrayer med blandade nycklar (PHPs `[0 => 'a', 'name' => 'b']`) blir vanliga objekt eftersom arrayer i JS måste ha ett sammanhängande heltalsindex.

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

Eftersom arrayer kopieras stannar ändringar 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
```

Objekt i PHP som inte är anropsbara konverteras inte; de kommer fram som `undefined` ([problem nr 18](https://github.com/inline0/phasis/issues/18)). Konvertera dem först till arrayer, till exempel med `get_object_vars()`, eller exponera deras metoder som closures. Se [Delade objekt](https://phasis.inline0.dev/sv/docs/interop/shared-objects).

## JS → PHP: resultat från `eval()` och `call()`

| Värde i JS | Resultat i PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (heltal inom int-intervallet) | `int` (`-0` blir `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (övriga) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | listan `array`, element konverteras rekursivt |
| `TypedArray` | lista `array` med tal |
| alla andra objekt (vanliga objekt, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | associativ `array` med objektets egna dataegenskaper; accessorer och egenskaper med funktionsvärden hoppas över |

Den sista raden betyder att objekt vars tillstånd finns i interna slots konverteras till en tom array: `new Date(0)`, `new Map([["a", 1]])` och `Promise.resolve(5)` returnerar alla `[]`. Ett `Error` returnerar sitt eget `message` och `stack`. Ett självrefererande objekt konverterar sina inre referenser till `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)');              // []
```

Konvertera inuti JS när du behöver något av dessa värden 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: argument till värdfunktioner

Argument som JS skickar till en värdfunktion i PHP följer samma regler för primitiva värden, arrayer och vanliga objekt, med följande skillnader:

| JS värde | PHP argument |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | motorns eget `JsValue`-objekt, okonverterat |
| objekt | associativ `array` över alla dess egna egenskaper, inklusive accessorer (vars getters körs) och egenskaper med funktionsvärden |
| pilfunktion | en `array` (vanligtvis tom) |
| `function` deklarerad med nyckelordet `function`, eller ett objekt som refererar till sig självt | ingen konvertering: kopieringen tar aldrig slut och PHP får slut på minne ([problem nr 21](https://github.com/inline0/phasis/issues/21)) |

Det går ännu inte att ta emot en callback i JS i en värdfunktion. Skicka data, inte funktioner, över den här gränsen.

## Tal och precision

Tal i JavaScript är IEEE 754-double. Heltal i PHP är plattformsbaserade (64 bitar i alla moderna byggen). Konverteringen bevarar heltal upp till ±2^53 exakt; större heltal i PHP blir `number` och förlorar precision efter 16 decimalsiffror. Använd `BigInt` inuti JS om du behöver exakt aritmetik med heltal större än så, och skicka ut resultatet som en sträng.

## Strängar

Strängar i PHP är byte-arrayer; strängar i JS är sekvenser av UTF-16-kodenheter. Motorn överbryggar skillnaden genom att tolka PHP byte som UTF-8 och koda om dem till UTF-16 internt. Detta är förlustfritt för giltig UTF-8-indata. Byte som inte är giltig UTF-8 skickas vidare oförändrade i stället för att ersättas med U+FFFD.

`String.length`, indexerad åtkomst (`s[0]`) och delsträngar räknar alla UTF-16-kodenheter enligt ECMAScript-specifikationen — inte kodpunkter eller byte. Strängar som innehåller astrala tecken som `'🍕'` har därför `.length === 2` (de två surrogathalvorna i U+1F355).

## Stabilitet vid rundresa

Vanliga data överlever en rundresa: ett värde i PHP som består av skalärer, listor och associativa arrayer och som går in via `setGlobal()` och kommer tillbaka via `eval()` eller `call()` ger samma struktur, förutom att flyttal med heltalsvärde kommer tillbaka som `int` (`1.0` returnerar `1`).

Allt annat i tabellerna ovan som mappas till `null`, `undefined` eller `[]` klarar inte en rundresa.
