Преобразование значений
Каждый вызов, пересекающий границу PHP↔JS — setGlobal, call или вызов хост-функции из JS — проходит через преобразователь:
- PHP → JS, когда значения хост-среды передаются в движок:
setGlobal(), аргументыcall(), возвращаемые хост-функциями значения. - JS → PHP, когда значения JS передаются обратно. Есть два пути с немного различающимися правилами: результаты
eval()иcall(), а также аргументы, которые JS передаёт хост-функции.
При преобразовании выполняется копирование. Примитивы преобразуются в примитивы, массивы и объекты копируются в новые структуры, а вызываемые объекты PHP оборачиваются в функции JS. По ссылке разделяются только сами вызываемые объекты.
PHP → JS
| Значение PHP | Результат JS |
|---|---|
null |
null |
true / false |
true / false |
int |
number (double IEEE 754; целые числа за пределами 2^53 теряют точность) |
float |
number (NaN, ±Infinity, ±0 сохраняются) |
string |
string (байты PHP UTF-8 интерпретируются как кодовые единицы UTF-16), даже если оно содержит имя функции PHP |
список array (ключи 0, 1, 2, … по порядку) |
новый Array, элементы преобразуются рекурсивно |
любой другой array |
новый обычный Object, значения преобразуются рекурсивно |
Closure, вызываемый объект, вызываемый массив, например [$object, 'method'] |
function, вызывающая вызываемый объект PHP |
любой другой объект, Generator, ресурс |
undefined |
Массивы со смешанными ключами (PHP: [0 => 'a', 'name' => 'b']) становятся обычными объектами, поскольку массивы в JS должны иметь непрерывный диапазон целочисленных индексов.
$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
Поскольку массивы копируются, изменения JS остаются в JS:
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list); // 3
$engine->eval('list.length'); // 4
Некликабельные объекты PHP не преобразуются и передаются как undefined (проблема №18). Сначала преобразуйте их в массивы, например с помощью get_object_vars(), или предоставьте их методы через замыкания. См. раздел Общие объекты.
JS → PHP: результаты eval() и call()
| Значение JS | Результат PHP |
|---|---|
undefined, null |
null |
boolean |
bool |
number (целое число в диапазоне int) |
int (-0 становится 0) |
NaN, Infinity, -Infinity |
NAN, INF, -INF |
number (другое) |
float |
string |
string (UTF-8) |
bigint, symbol |
null |
function |
null |
Array |
список array, элементы преобразуются рекурсивно |
TypedArray |
список array чисел |
любой другой объект (обычные объекты, Error, Date, Map, Set, Promise, ArrayBuffer, …) |
ассоциативный array из собственных свойств данных, аксессоры и свойства-функции пропускаются |
Последняя строка означает, что объекты, хранящие состояние во внутренних слотах, преобразуются в пустой массив: new Date(0), new Map([["a", 1]]) и Promise.resolve(5) возвращают []. Объект Error возвращает собственные message и stack. Ссылки внутри объекта на него самого преобразуются в null.
$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)'); // []
Преобразуйте значение внутри JS, если оно нужно вам в 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: аргументы хост-функций
Аргументы, которые JS передаёт хост-функции PHP, преобразуются по тем же правилам для примитивов, массивов и обычных объектов, за исключением следующего:
| Значение JS | Аргумент PHP |
|---|---|
TypedArray, ArrayBuffer, DataView |
собственный объект JsValue движка, без преобразования |
| объект | ассоциативный array со всеми собственными свойствами, включая аксессоры (их геттеры вызываются) и свойства-функции |
| стрелочная функция | array (обычно пустой) |
function, объявленная с ключевым словом function, или любой объект со ссылкой на себя |
не преобразуется: копирование никогда не завершается, и PHP заканчивается память (проблема №21) |
Пока нет способа получить обратный вызов JS в хост-функции. Передавайте через эту границу данные, а не функции.
Числа и точность
Числа JavaScript — это числа с плавающей запятой двойной точности IEEE 754. Целые числа PHP имеют платформенный формат (во всех современных сборках — 64-разрядный). При преобразовании целые числа до ±2^53 сохраняются точно; более крупные целые числа PHP становятся number и теряют точность после 16 десятичных цифр. Для точных вычислений с более крупными целыми числами используйте BigInt внутри JS, а результат передавайте в виде строки.
Строки
Строки PHP — это массивы байтов; строки JS — последовательности кодовых единиц UTF-16. Движок соединяет их, интерпретируя байты PHP как UTF-8 и перекодируя их во внутреннее представление UTF-16. Для корректных входных данных UTF-8 преобразование выполняется без потерь. Байты, не являющиеся корректным UTF-8, передаются без изменений, а не заменяются на U+FFFD.
String.length, индексированный доступ (s[0]) и извлечение среза считают кодовые единицы UTF-16 согласно спецификации ECMAScript, а не кодовые точки и не байты. Поэтому строки с символами за пределами базовой многоязычной плоскости, например '🍕', имеют .length === 2 — две суррогатные половины U+1F355.
Стабильность при обратном преобразовании
Обычные данные сохраняются при обратном преобразовании: значение PHP, состоящее из скаляров, списков и ассоциативных массивов, которое передаётся через setGlobal() и возвращается через eval() или call(), сохраняет исходную структуру. Исключение: целые числа с плавающей точкой возвращаются как int (1.0 возвращает 1).
Все остальные значения из таблиц выше, которые преобразуются в null, undefined или [], не сохраняются при обратном преобразовании.