
# تحويل القيم

كل استدعاء يعبر حدود 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` ([المسألة #18](https://github.com/inline0/phasis/issues/18)). حوّلها إلى مصفوفات أولًا، مثلًا باستخدام `get_object_vars()`، أو أتِح أساليبها عبر الإغلاقات. راجع [الكائنات المشتركة](https://phasis.inline0.dev/ar/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)');              // []
```

حوّل هذه القيم داخل 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` ترابطية لكل خصائصه الذاتية، بما فيها خصائص الوصول (التي تُشغّل دوال getter الخاصة بها) والخصائص التي تكون قيمها دوال |
| دالة سهمية | `array` (فارغة عادةً) |
| `function` معرّف بالكلمة المفتاحية `function` أو أي كائن يشير إلى نفسه | لا تحويل: لا تنتهي عملية النسخ مطلقًا وتنُفد ذاكرة PHP ([المسألة #21](https://github.com/inline0/phasis/issues/21)) |

لا توجد حتى الآن طريقة لاستقبال رد نداء JS في دالة مضيف. مرّر البيانات، لا الدوال، عبر هذه الحدود.

## الأعداد والدقة

أعداد JavaScript أعداد عشرية مزدوجة وفق IEEE 754. أما أعداد PHP الصحيحة فهي أصلية للمنصة (64 بت في كل بنية حديثة). يحافظ التحويل على دقة الأعداد الصحيحة حتى ±2^53؛ أما أعداد PHP الصحيحة الأكبر فتتحول إلى `number` وتفقد الدقة بعد 16 خانة عشرية. استخدم `BigInt` داخل JS إذا احتجت إلى حساب دقيق لأعداد صحيحة أكبر، ومرّر النتيجة كسلسلة.

## السلاسل

سلاسل PHP مصفوفات بايتات؛ أما سلاسل JS فهي تسلسلات من وحدات شيفرة UTF-16. يصل المحرك بينهما بتفسير بايتات PHP على أنها UTF-8، ثم يعيد ترميزها داخليًا إلى UTF-16. لا تُفقد البيانات عند إدخال UTF-8 صالح. أما البايتات غير الصالحة بترميز UTF-8 فتمر كما هي بدلًا من استبدالها بـ U+FFFD.

`String.length`، والوصول المفهرس (`s[0]`)، والتقطيع كلها تحسب وحدات UTF-16 code units وفقًا لمواصفة ECMAScript، لا نقاط الترميز ولا البايتات. لذلك فإن السلاسل التي تحتوي على محارف خارج النطاق الأساسي مثل `'🍕'` لها `.length === 2` (نصفي الاستبدال لـ U+1F355).

## استقرار الذهاب والإياب

تظل البيانات العادية كما هي بعد جولة ذهاب وإياب: فقيمة PHP المؤلفة من قيم مفردة وقوائم ومصفوفات ترابطية، عند تمريرها عبر `setGlobal()` ثم إعادتها عبر `eval()` أو `call()`، تنتج البنية نفسها، باستثناء أن الأعداد العشرية الصحيحة تعود بصيغة `int` (تعيد `1.0` القيمة `1`).

أما كل ما عدا ذلك في الجداول أعلاه مما يتحول إلى `null` أو `undefined` أو `[]` فلا يعود إلى صيغته الأصلية.
