
# 値の変換

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の配列では、整数インデックスが連続していなければならないためです。

```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](https://github.com/inline0/phasis/issues/18)）。たとえば`get_object_vars()`で配列に変換するか、メソッドをクロージャとして公開してください。[共有オブジェクト](https://phasis.inline0.dev/ja/docs/interop/shared-objects)を参照してください。

## 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)');              // []
```

これらの値をPHPで使う必要がある場合は、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](https://github.com/inline0/phasis/issues/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`、`[]`にマッピングされるその他の値は、往復変換で元に戻りません。
