
# Conversion des valeurs

Tout appel qui franchit la frontière PHP↔JS — `setGlobal`, `call`, une fonction hôte appelée depuis JS — passe par un convertisseur :

- **PHP → JS** lorsque les valeurs hôtes entrent dans le moteur (arguments de `setGlobal()`, `call()` et valeurs renvoyées par les fonctions hôtes).
- **JS → PHP** lorsque les valeurs JS repartent vers l’extérieur. Deux chemins suivent des règles légèrement différentes : les résultats de `eval()` et `call()`, et les arguments que JS transmet à une fonction hôte.

La conversion effectue des copies. Les primitives deviennent des primitives, les tableaux et objets sont copiés dans de nouvelles structures, et les fonctions appelables PHP sont enveloppées comme fonctions JS. Seules les fonctions appelables elles-mêmes sont partagées par référence.

## PHP → JS

| Valeur PHP | Résultat JS |
|---|---|
| `null` | `null` |
| `true` / `false` | `true` / `false` |
| `int` | `number` (double IEEE 754 ; les entiers au-delà de 2^53 perdent en précision) |
| `float` | `number` (NaN, ±Infinity et ±0 sont préservés) |
| `string` | `string` (octets UTF-8 PHP interprétés comme unités de code UTF-16), même s’il désigne une fonction PHP |
| liste `array` (clés 0, 1, 2, … dans l’ordre) | nouveau `Array`, éléments convertis récursivement |
| tout autre `array` | nouveau `Object` simple, valeurs converties récursivement |
| `Closure`, objet invocable, tableau appelable tel que `[$object, 'method']` | `function` qui appelle la fonction appelable PHP |
| tout autre objet, `Generator`, ressource | `undefined` |

Les tableaux à clés mixtes (PHP `[0 => 'a', 'name' => 'b']`) deviennent des objets simples, car les tableaux dans JS doivent avoir des indices entiers contigus.

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

Comme les tableaux sont copiés, les modifications apportées à JS restent dans JS :

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

Les objets PHP qui ne sont pas appelables ne sont pas convertis ; ils arrivent sous forme de `undefined` ([issue #18](https://github.com/inline0/phasis/issues/18)). Convertissez-les d’abord en tableaux, par exemple avec `get_object_vars()`, ou exposez leurs méthodes sous forme de closures. Voir [Objets partagés](https://phasis.inline0.dev/fr/docs/interop/shared-objects).

## JS → PHP : résultats de `eval()` et `call()`

| Valeur JS | Résultat PHP |
|---|---|
| `undefined`, `null` | `null` |
| `boolean` | `bool` |
| `number` (entier, dans la plage des entiers) | `int` (`-0` devient `0`) |
| `NaN`, `Infinity`, `-Infinity` | `NAN`, `INF`, `-INF` |
| `number` (autre) | `float` |
| `string` | `string` (UTF-8) |
| `bigint`, `symbol` | `null` |
| `function` | `null` |
| `Array` | liste `array`, éléments convertis récursivement |
| `TypedArray` | liste `array` de nombres |
| tout autre objet (objets simples, `Error`, `Date`, `Map`, `Set`, `Promise`, `ArrayBuffer`, …) | `array` associatif de ses propriétés de données propres, en ignorant les accesseurs et les propriétés contenant des fonctions |

La dernière ligne signifie que les objets dont l’état réside dans des slots internes deviennent des tableaux vides : `new Date(0)`, `new Map([["a", 1]])` et `Promise.resolve(5)` renvoient tous `[]`. Un `Error` renvoie ses propres `message` et `stack`. Les références internes d’un objet autoréférentiel deviennent `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)');              // []
```

Effectuez la conversion dans JS lorsque vous avez besoin de l’une de ces valeurs dans 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 : arguments des fonctions hôtes

Les arguments que JS transmet à une fonction hôte PHP suivent les mêmes règles pour les primitives, les tableaux et les objets simples, avec les différences suivantes :

| Valeur JS | Argument PHP |
|---|---|
| `TypedArray`, `ArrayBuffer`, `DataView` | objet `JsValue` du moteur, non converti |
| objet | `array` associatif de toutes ses propriétés propres, y compris les accesseurs (dont les getters sont exécutés) et les propriétés contenant des fonctions |
| fonction fléchée | un `array` (généralement vide) |
| `function` déclaré avec le mot-clé `function`, ou tout objet qui se référence lui-même | pas de conversion : la copie ne se termine jamais et PHP finit par manquer de mémoire ([issue #21](https://github.com/inline0/phasis/issues/21)) |

Il n’est pas encore possible de recevoir une fonction de rappel JS dans une fonction hôte. Faites passer des données, et non des fonctions, par cette frontière.

## Nombres et précision

Les nombres JavaScript sont des doubles IEEE 754. Les entiers PHP sont natifs de la plateforme (64 bits dans toutes les versions modernes). La conversion préserve exactement les entiers jusqu’à ±2^53 ; les entiers PHP plus grands deviennent des `number` et perdent en précision au-delà de 16 chiffres décimaux. Utilisez `BigInt` dans JS pour effectuer des calculs exacts sur des entiers plus grands, puis transmettez le résultat sous forme de chaîne.

## Chaînes

Les chaînes PHP sont des tableaux d’octets ; les chaînes JS sont des séquences d’unités de code UTF-16. Le moteur fait le lien en interprétant les octets PHP comme de l’UTF-8, puis en les réencodant en interne en UTF-16. Cette conversion est sans perte pour les entrées UTF-8 valides. Les octets qui ne sont pas de l’UTF-8 valide sont transmis tels quels au lieu d’être remplacés par U+FFFD.

`String.length`, l’accès par index (`s[0]`) et le découpage comptent tous les unités de code UTF-16, conformément à la spécification ECMAScript — et non les points de code ni les octets. Les chaînes contenant des caractères hors du plan multilingue de base, comme `'🍕'`, ont donc `.length === 2` (les deux moitiés de la paire de substitution de U+1F355).

## Stabilité aller-retour

Les données simples survivent à un aller-retour : une valeur PHP composée de scalaires, de listes et de tableaux associatifs, transmise via `setGlobal()` puis récupérée via `eval()` ou `call()`, conserve la même structure, à ceci près que les nombres à virgule flottante entiers reviennent sous forme de `int` (`1.0` renvoie `1`).

Tout le reste dans les tableaux ci-dessus qui devient `null`, `undefined` ou `[]` ne survit pas à un aller-retour.
