
# Objetos compartilhados

Passar um objeto PHP por referência para JavaScript — ler suas propriedades, chamar seus métodos e ver as alterações em PHP — **ainda não é compatível**. Isso está registrado em [problema #18](https://github.com/inline0/phasis/issues/18). Esta página descreve o comportamento atual e os padrões que funcionam no lugar disso.

## O que acontece atualmente

`setGlobal()` converte seu valor segundo as regras de [Conversão de valores](https://phasis.inline0.dev/pt/docs/interop/values). Um objeto PHP que não é callable não tem conversão, então o global é `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')
```

O mesmo vale para `stdClass`, entidades e modelos, contêineres de serviços, geradores e recursos. Há duas exceções: objetos callable:

- Um objeto com `__invoke()` torna-se uma função JS que o chama.
- Um array callable como `[$counter, 'increment']` torna-se uma função JS que chama esse método.

Não há `PhpBridge` nem outra classe wrapper para expor objetos; `src/Interop/` contém apenas o conversor de valores.

## Exponha métodos como closures

Uma closure pode capturar o objeto, então JS chama uma função e o método é executado na instância PHP original:

```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
```

Um array callable faz o mesmo sem uma closure:

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

## Agrupe-os em um array

Um array associativo torna-se um objeto JS, e as closures dentro dele tornam-se funções. Assim, JS recebe um objeto API com métodos que agem sobre a instância 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
```

Somente o que você colocar no array fica visível para JS, então o estado privado, como `$secret`, permanece em PHP.

Os campos de dados são cópias feitas quando `setGlobal()` é executado. No exemplo, `user.name` é uma string em JS; atribuir um valor a ela altera a cópia JS, não `$user->name`. Encaminhe todas as gravações por uma closure.

## Envie dados e receba resultados

Para objetos que representam dados, converta-os em um array na entrada e leia o resultado na saída:

```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)` ou um método `toArray()` fornece o array de um objeto. Em seguida, aplique o array retornado ao seu objeto, entidade ou modelo em PHP.

## Observações

- Atualmente, exceções lançadas dentro dessas closures não podem ser capturadas por JS `try` / `catch`; elas chegam ao chamador de `eval()` ([problema #20](https://github.com/inline0/phasis/issues/20)). Consulte [Funções hospedeiras](https://phasis.inline0.dev/pt/docs/interop/host-functions#throwing-back-into-js).
- Uma closure registrada com `setGlobal()` mantém vivos os objetos que captura enquanto o mecanismo mantiver o global.
