
# Спільні об’єкти

Передавання об’єкта PHP у JavaScript за посиланням — читання його властивостей, виклик його методів і відстеження змін у PHP — **поки що не підтримується**. Це відстежується в [помилці №18](https://github.com/inline0/phasis/issues/18). На цій сторінці описано, що відбувається зараз, і наведено робочі підходи.

## Що відбувається зараз

`setGlobal()` перетворює значення за правилами з розділу [Перетворення значень](https://phasis.inline0.dev/uk/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.

## Застереження

- Винятки, викинуті всередині цих замикань, наразі не перехоплюються в JS `try` / `catch`; вони доходять до місця виклику `eval()` ([помилка №20](https://github.com/inline0/phasis/issues/20)). Див. розділ [Функції хост-середовища](https://phasis.inline0.dev/uk/docs/interop/host-functions#throwing-back-into-js).
- Зареєстроване через `setGlobal()` замикання підтримує об’єкти, які воно захопило, доки рушій утримує глобальну змінну.
