
# Objetos compartidos

Aún no se admite pasar un objeto PHP por referencia a JavaScript —leer sus propiedades, llamar a sus métodos y ver los cambios en PHP. El tema se sigue en la [incidencia n.º 18](https://github.com/inline0/phasis/issues/18). Esta página describe el comportamiento actual y los patrones que sí funcionan.

## Qué ocurre actualmente

`setGlobal()` convierte su valor según las reglas de [Conversión de valores](https://phasis.inline0.dev/es/docs/interop/values). Un objeto PHP que no se puede invocar no tiene conversión, así que la variable global es `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')
```

Lo mismo se aplica a `stdClass`, entidades y modelos, contenedores de servicios, generadores y recursos. Hay dos excepciones: los objetos invocables:

- Un objeto con `__invoke()` se convierte en una función JS que lo llama.
- Un array invocable, como `[$counter, 'increment']`, se convierte en una función JS que llama a ese método.

No existe una clase contenedora `PhpBridge` ni otra para exponer objetos; `src/Interop/` solo contiene el conversor de valores.

## Exponer métodos como cierres

Un cierre puede capturar el objeto; así, JS llama a una función y el método se ejecuta en la instancia 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
```

Un array invocable hace lo mismo sin un cierre:

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

## Agruparlos en un array

Un array asociativo se convierte en un objeto JS y los cierres que contiene se convierten en funciones. Así, JS obtiene un API con forma de objeto cuyos métodos actúan sobre la instancia 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 solo puede ver lo que incluyas en el array, así que el estado privado, como `$secret`, permanece en PHP.

Los campos de datos son copias tomadas cuando se ejecuta `setGlobal()`. En el ejemplo, `user.name` es una cadena en JS; asignarle otro valor cambia la copia de JS, no `$user->name`. Canaliza todas las escrituras mediante un cierre.

## Pasar datos y devolver resultados

Para objetos con estructura de datos, conviértalos en un array al introducirlos y lea el resultado al extraerlo:

```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)` o un método `toArray()` le proporciona el array de un objeto. Después, aplique el array devuelto a su objeto, entidad o modelo en PHP.

## Advertencias

- Las excepciones lanzadas dentro de estos cierres actualmente no pueden ser capturadas por JS `try` / `catch`; llegan al llamador de `eval()` ([incidencia #20](https://github.com/inline0/phasis/issues/20)). Consulte [Funciones del host](https://phasis.inline0.dev/es/docs/interop/host-functions#throwing-back-into-js).
- Un cierre registrado con `setGlobal()` mantiene vivos los objetos que captura mientras el motor conserve el global.
