
# Общие объекты

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

## Что происходит сейчас

`setGlobal()` преобразует значение по правилам из раздела [Преобразование значений](https://phasis.inline0.dev/ru/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/ru/docs/interop/host-functions#throwing-back-into-js).
- Зарегистрированное с помощью `setGlobal()` замыкание не позволяет объектам, которые оно захватило, удалиться, пока движок хранит глобальную переменную.
