Aller au contenu

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). 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.

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)

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.