Перетворення значень
Кожен виклик через межу 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 мають використовувати суцільну послідовність цілих індексів.
$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 — послідовності 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 або [], не зберігається після зворотного перетворення.