Zum Inhalt springen

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:

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). Siehe Host-Funktionen.
  • Ein mit setGlobal() registrierter Closure hält die erfassten Objekte am Leben, solange die Engine das globale Objekt hält.