Перейти до вмісту

Перетворення значень

Кожен виклик через межу 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 (число IEEE 754 double; цілі числа понад 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 — послідовності 16-бітних кодових одиниць. Рушій з’єднує їх, трактуючи байти PHP як UTF-8 і повторно кодуючи їх у UTF-16. Для коректних вхідних даних UTF-8 перетворення не призводить до втрат. Байти, що не є коректним UTF-8, передаються без змін, а не замінюються на U+FFFD.

String.length, доступ за індексом (s[0]) і отримання зрізу рахують 16-бітні кодові одиниці UTF-16 згідно зі специфікацією ECMAScript — не кодові точки й не байти. Тому рядки з символами поза базовою багатомовною площиною, як-от '🍕', мають .length === 2 (дві сурогатні половини U+1F355).

Стабільність зворотного перетворення

Звичайні дані переживають перетворення туди й назад: значення PHP зі скалярів, списків і асоціативних масивів, передане через setGlobal() і повернуте через eval() або call(), зберігає ту саму структуру, окрім того, що цілі числа з рухомою комою повертаються як int (1.0 повертає 1).

Усе інше з наведених вище таблиць, що перетворюється на null, undefined або [], не зберігається після зворотного перетворення.