Към съдържанието

Преобразуване на стойности

Всяко извикване, което преминава границата 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 трябва да имат непрекъснат диапазон от цели индекси.

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 (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.

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 свършва паметта (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 или [], не запазва стойността си при обратно преобразуване.