
# الكائنات المشتركة

تمرير كائن PHP إلى JavaScript بالمرجع — وقراءة خصائصه واستدعاء أساليبه ورؤية التغييرات في PHP — **غير مدعوم بعد**. يُتابع ذلك في [المسألة #18](https://github.com/inline0/phasis/issues/18). تصف هذه الصفحة ما يحدث حاليًا والأنماط البديلة المناسبة.

## ما يحدث حاليًا

يحوّل `setGlobal()` قيمته وفق القواعد الواردة في [تحويل القيم](https://phasis.inline0.dev/ar/docs/interop/values). لا يملك كائن PHP غير القابل للاستدعاء قاعدة تحويل، لذا تكون القيمة العامة `undefined`:

```php
class Counter {
    public int $value = 0;
    public function increment(): void { $this->value++; }
}

$counter = new Counter();
$engine->setGlobal('counter', $counter);

$engine->eval('typeof counter');
// "undefined"

$engine->eval('counter.increment()');
// throws Phasis\Exceptions\TypeError:
// Cannot read properties of undefined (reading 'increment')
```

ينطبق الأمر نفسه على `stdClass` والكيانات والنماذج وحاويات الخدمات والمولدات والموارد. والاستثناءان هما الكائنات القابلة للاستدعاء:

- يتحول كائن يتضمن `__invoke()` إلى دالة JS تستدعيه.
- تتحول المصفوفة القابلة للاستدعاء مثل `[$counter, 'increment']` إلى دالة JS تستدعي ذلك الأسلوب.

لا توجد فئة غلاف `PhpBridge` أو فئة أخرى لإتاحة الكائنات؛ تحتوي `src/Interop/` على محوّل القيم فقط.

## إتاحة الأساليب عبر الإغلاقات

يمكن للإغلاق التقاط الكائن، بحيث يستدعي JS دالةً وينفّذ الأسلوب على مثيل PHP الأصلي:

```php
$counter = new Counter();
$engine->setGlobal('increment', fn () => $counter->increment());
$engine->setGlobal('count', fn () => $counter->value);

$engine->eval('increment(); increment(); count()');
// 2

echo $counter->value;
// 2
```

تؤدي المصفوفة القابلة للاستدعاء الغرض نفسه دون إغلاق:

```php
$engine->setGlobal('increment', [$counter, 'increment']);
```

## تجميعها في مصفوفة

تتحول المصفوفة الترابطية إلى كائن JS، وتتحول الإغلاقات الموجودة فيها إلى دوال. يمنح ذلك JS واجهة API شبيهة بالكائن، تعمل أساليبها على مثيل PHP:

```php
class User {
    public function __construct(
        public string $name,
        public int $age,
        private string $secret,
    ) {}

    public function greet(): string {
        return "Hi, I'm $this->name";
    }
}

$user = new User('Ada', 36, 'topsecret');

$engine->setGlobal('user', [
    'name'   => $user->name,
    'greet'  => fn () => $user->greet(),
    'setAge' => function (int $age) use ($user): void { $user->age = $age; },
]);

$engine->eval('user.greet()');
// "Hi, I'm Ada"

$engine->eval('user.setAge(37)');
echo $user->age;
// 37
```

لا يظهر لـJS سوى ما تضعه في المصفوفة، لذا تبقى الحالة الخاصة مثل `$secret` داخل PHP.

حقول البيانات نُسخ تؤخذ عند تشغيل `setGlobal()`. في المثال، تكون `user.name` سلسلة في JS؛ وإسناد قيمة إليها يغيّر نسخة JS لا `$user->name`. مرّر كل عمليات الكتابة عبر إغلاق.

## تمرير البيانات وإعادة النتائج

بالنسبة إلى الكائنات التي تمثل بيانات، حوّلها إلى مصفوفة عند تمريرها، ثم اقرأ النتيجة عند إعادتها:

```php
$post = ['title' => 'Draft', 'tags' => ['php']];

$engine->setGlobal('post', $post);
$engine->eval('function edit(p) { p.title = "Edited from JS"; return p; }');

$post = $engine->call('edit', $post);
echo $post['title'];
// Edited from JS
```

تمنحك `get_object_vars($object)` أو إحدى طرق `toArray()` مصفوفةً لكائن. ثم طبّق المصفوفة المعادة على كائنك أو كيانك أو نموذجك في PHP.

## ملاحظات

- لا يمكن حاليًا التقاط الاستثناءات التي تُرمى داخل هذه الإغلاقات باستخدام `try` / `catch` في JS؛ بل تصل إلى مستدعي `eval()` ([المسألة #20](https://github.com/inline0/phasis/issues/20)). راجع [دوال المضيف](https://phasis.inline0.dev/ar/docs/interop/host-functions#throwing-back-into-js).
- يُبقي الإغلاق المسجل عبر `setGlobal()` الكائنات التي يلتقطها حيةً ما دام المحرك يحتفظ بالقيمة العامة.
