Перейти к содержимому

Преобразование значений

Каждый вызов, пересекающий границу 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 должны иметь непрерывный диапазон целочисленных индексов.

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

Поскольку массивы копируются, изменения JS остаются в JS:

PHP
$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.

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)');              // []

Преобразуйте значение внутри JS, если оно нужно вам в 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: аргументы хост-функций

Аргументы, которые 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 или [], не сохраняются при обратном преобразовании.