Преобразуване на стойности
Всяко извикване, което преминава границата 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; цели числа над 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 (issue #18). Първо ги преобразувайте в масиви, например с get_object_vars(), или предоставете методите им като затваряния. Вижте Споделени обекти.
JS → PHP: резултати eval() и call()
| Стойност JS | Резултат PHP |
|---|---|
undefined, null |
null |
boolean |
bool |
number (цяло число в допустимия диапазон) |
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 свършва паметта (issue #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 или [], не запазва стойността си при обратно преобразуване.