
# Gemeinsam genutzte Objekte

Ein PHP-Objekt per Referenz an JavaScript zu übergeben, um seine Eigenschaften zu lesen, Methoden aufzurufen und Änderungen in PHP zu sehen, wird **noch nicht unterstützt**. Der Stand dazu wird in [Issue #18](https://github.com/inline0/phasis/issues/18) verfolgt. Diese Seite beschreibt das aktuelle Verhalten und geeignete Alternativen.

## Aktuelles Verhalten

`setGlobal()` konvertiert den Wert nach den Regeln unter [Wertkonvertierung](https://phasis.inline0.dev/de/docs/interop/values). Für ein nicht aufrufbares PHP-Objekt gibt es keine Konvertierung; daher ist der globale Wert `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')
```

Dasselbe gilt für `stdClass`, Entitäten und Modelle, Service-Container, Generatoren und Ressourcen. Zwei Ausnahmen sind aufrufbare Objekte:

- Ein Objekt mit `__invoke()` wird zu einer JS-Funktion, die es aufruft.
- Ein aufrufbares Array wie `[$counter, 'increment']` wird zu einer JS-Funktion, die diese Methode aufruft.

Es gibt weder `PhpBridge` noch eine andere Wrapper-Klasse, um Objekte bereitzustellen; `src/Interop/` enthält nur den Wertkonverter.

## Methoden als Closures bereitstellen

Ein Closure kann das Objekt erfassen, sodass JS eine Funktion aufruft und die Methode auf der ursprünglichen PHP-Instanz ausgeführt wird:

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

Ein aufrufbares Array erreicht dasselbe ohne Closure:

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

## In einem Array gruppieren

Ein assoziatives Array wird zu einem JS-Objekt, und darin enthaltene Closures werden zu Funktionen. So erhält JS ein objektähnliches API, dessen Methoden auf der PHP-Instanz arbeiten:

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

Für JS ist nur sichtbar, was du in das Array einfügst. Privater Zustand wie `$secret` bleibt daher in PHP.

Datenfelder sind Kopien, die beim Ausführen von `setGlobal()` erstellt werden. Im Beispiel ist `user.name` in JS eine Zeichenfolge; wenn du ihr einen Wert zuweist, ändert sich die JS-Kopie, nicht `$user->name`. Leite jeden Schreibvorgang über einen Closure.

## Daten übergeben und Ergebnisse zurückgeben

Bei Objekten mit Datenstruktur wandelst du sie beim Übergeben in ein Array um und liest das Ergebnis beim Zurückgeben aus:

```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)` oder eine `toArray()`-Methode liefert dir das Array eines Objekts. Wende das zurückgegebene Array anschließend in PHP auf dein Objekt, deine Entität oder dein Modell an.

## Einschränkungen

- Ausnahmen, die innerhalb dieser Closures ausgelöst werden, kann JS derzeit nicht mit `try` / `catch` abfangen; sie erreichen den Aufrufer von `eval()` ([Issue #20](https://github.com/inline0/phasis/issues/20)). Siehe [Host-Funktionen](https://phasis.inline0.dev/de/docs/interop/host-functions#throwing-back-into-js).
- Ein mit `setGlobal()` registrierter Closure hält die erfassten Objekte am Leben, solange die Engine das globale Objekt hält.
