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 verfolgt. Diese Seite beschreibt das aktuelle Verhalten und geeignete Alternativen.
Aktuelles Verhalten
setGlobal() konvertiert den Wert nach den Regeln unter Wertkonvertierung. Für ein nicht aufrufbares PHP-Objekt gibt es keine Konvertierung; daher ist der globale Wert undefined:
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:
$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:
$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:
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:
$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/catchabfangen; sie erreichen den Aufrufer voneval()(Issue #20). Siehe Host-Funktionen. - Ein mit
setGlobal()registrierter Closure hält die erfassten Objekte am Leben, solange die Engine das globale Objekt hält.