Ga naar de inhoud

Gedeelde objecten

Een object van PHP via een referentie doorgeven aan JavaScript, de eigenschappen ervan lezen, de methoden aanroepen en wijzigingen zien in PHP — dat wordt nog niet ondersteund. Dit wordt bijgehouden in issue #18. Deze pagina beschrijft wat er momenteel gebeurt en welke patronen wel werken.

Wat er momenteel gebeurt

setGlobal() zet de waarde om volgens de regels in Waard-conversie. Een object van PHP dat niet callable is, kan niet worden geconverteerd; daarom is de globale variabele 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')

Hetzelfde geldt voor stdClass, entiteiten en modellen, servicecontainers, generators en resources. Twee uitzonderingen zijn callable objecten:

  • Een object met __invoke() wordt een functie van JS die het aanroept.
  • Een callable array zoals [$counter, 'increment'] wordt een functie van JS die die methode aanroept.

Er is geen PhpBridge- of andere wrapperklasse om objecten beschikbaar te maken; src/Interop/ bevat alleen de waardeconverter.

Methoden als closures beschikbaar maken

Een closure kan het object vastleggen, zodat JS een functie aanroept en de methode wordt uitgevoerd op de oorspronkelijke instantie van PHP:

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

Een callable array doet hetzelfde zonder closure:

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

Groepeer ze in een array

Een associatieve array wordt een object van JS, en closures erin worden functies. Zo krijgt JS een objectvormige API waarvan de methoden werken op de instantie van 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

Alleen wat je in de array plaatst, is zichtbaar voor JS. Privéstatus zoals $secret blijft dus in PHP.

De datavelden zijn kopieën die worden gemaakt wanneer setGlobal() wordt uitgevoerd. In het voorbeeld is user.name een tekenreeks in JS; als je die wijzigt, verandert de kopie van JS, niet $user->name. Laat alle schrijfbewerkingen via een closure lopen.

Gegevens doorgeven en resultaten terugkrijgen

Zet objecten met een gegevensstructuur om naar een array wanneer je ze doorgeeft en lees het resultaat wanneer je ze terugkrijgt:

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) of een methode van toArray() geeft je de array voor een object. Pas de geretourneerde array vervolgens in PHP toe op je object, entiteit of model.

Kanttekeningen

  • Uitzonderingen die binnen deze closures worden gegooid, kunnen momenteel niet worden opgevangen door JS try / catch; ze bereiken de aanroeper van eval() (issue #20). Zie Hostfuncties.
  • Een closure die met setGlobal() is geregistreerd, houdt de objecten die deze vastlegt in leven zolang de engine de globale variabele bewaart.