値の変換
PHP↔JS境界を越える呼び出し(setGlobal、call、JSから呼び出されるホスト関数)はすべて、変換器を通ります:
- PHP → JS は、ホスト側の値がエンジンに渡されるときに適用されます(
setGlobal()、call()の引数、ホスト関数の戻り値)。 - JS → PHP は、JSの値が外に返されるときに適用されます。ルールが少し異なる経路が2つあります。
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(UTF-8のPHPバイトをUTF-16コード単位として解釈)。PHP関数の名前であっても同様です |
リストarray(キー0、1、2、…の順) |
新しいArray。要素は再帰的に変換されます |
その他のarray |
新しい通常のObject。値は再帰的に変換されます |
Closure、呼び出し可能オブジェクト、[$object, 'method']などの呼び出し可能配列 |
PHPの呼び出し可能オブジェクトを呼び出すfunction |
その他のオブジェクト、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)'); // []
これらの値をPHPで使う必要がある場合は、JS内で変換してください:
$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桁を超える桁で精度が失われます。それより大きい整数を正確に計算する必要がある場合は、JS内でBigIntを使い、結果を文字列として渡してください。
文字列
PHPの文字列はバイト配列ですが、JS文字列はUTF-16コード単位の並びです。エンジンはPHPのバイトをUTF-8として解釈し、内部でUTF-16に再エンコードして両者を橋渡しします。有効なUTF-8入力であれば、情報は失われません。有効なUTF-8でないバイトは、U+FFFDに置き換えず、そのまま渡されます。
String.length、インデックスアクセス(s[0])、スライスはいずれもECMAScript仕様に従ってUTF-16コード単位を数えます。コードポイントやバイト数ではありません。そのため、'🍕'のような補助文字を含む文字列では、.length === 2(U+1F355のサロゲートペア)が生じます。
往復変換の安定性
スカラー値、リスト、連想配列で構成されたPHP値は、setGlobal()経由で入力し、eval()またはcall()経由で戻すと、整数値の浮動小数点数がintとして戻る点を除き、同じ構造を保ちます(1.0は1を返します)。
上の表でnull、undefined、[]にマッピングされるその他の値は、往復変換で元に戻りません。