
# Споделени обекти

Подаването на обект PHP към JavaScript по референция — четене на свойствата му, извикване на методите му и виждане на промените в PHP — **все още не се поддържа**. Това се проследява в [issue #18](https://github.com/inline0/phasis/issues/18). Тази страница описва какво се случва днес и кои модели работят вместо това.

## Какво се случва днес

`setGlobal()` преобразува стойността си според правилата в [Преобразуване на стойности](https://phasis.inline0.dev/bg/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()` ([issue #20](https://github.com/inline0/phasis/issues/20)). Вижте [Хост функции](https://phasis.inline0.dev/bg/docs/interop/host-functions#throwing-back-into-js).
- Затваряне, регистрирано с `setGlobal()`, запазва обектите, които обхваща, докато енджинът държи глобалната стойност.
