Gå til indhold

Delte objekter

Det er endnu ikke understøttet at sende et PHP-objekt til JavaScript som reference, læse dets egenskaber, kalde dets metoder og se ændringerne i PHP. Det spores i issue #18. Denne side beskriver, hvad der sker i dag, og hvilke mønstre der virker i stedet.

Hvad der sker i dag

setGlobal() konverterer værdien efter reglerne i Værdikonvertering. Et PHP-objekt, der ikke kan kaldes, har ingen konvertering, så den globale variabel bliver 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')

Det samme gælder stdClass, entiteter og modeller, servicecontainere, generatorer og ressourcer. To undtagelser er objekter, der kan kaldes:

  • Et objekt med __invoke() bliver til en JS-funktion, der kalder det.
  • Et callable array som [$counter, 'increment'] bliver til en JS-funktion, der kalder den pågældende metode.

Der findes ingen PhpBridge eller anden wrapperklasse til eksponering af objekter; src/Interop/ indeholder kun værdikonverteren.

Eksponér metoder som closure-funktioner

En closure-funktion kan indfange objektet, så JS kalder en funktion, og metoden kører på den oprindelige PHP-instans:

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

Et callable array gør det samme uden en closure-funktion:

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

Saml dem i et array

Et associativt array bliver til et JS-objekt, og closure-funktionerne i det bliver til funktioner. Dermed får JS et API med objektform, hvis metoder arbejder på PHP-instansen:

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

Kun det, du lægger i arrayet, er synligt for JS, så privat tilstand som $secret forbliver i PHP.

Datafelterne er kopier, der tages, når setGlobal() kører. I eksemplet er user.name en streng i JS; hvis du tildeler den en værdi, ændres JS-kopien, ikke $user->name. Send alle skrivninger gennem en closure-funktion.

Send data ud, og resultater tilbage

For dataformede objekter skal du konvertere til et array på vej ind og læse resultatet på vej ud:

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) eller en toArray()-metode giver dig arrayet for et objekt. Anvend derefter det returnerede array på dit objekt, din entitet eller din model i PHP.

Forbehold

  • Undtagelser, der kastes inde i disse closure-funktioner, kan i øjeblikket ikke fanges af JS try / catch; de når den, der kaldte eval() (issue #20). Se Værtsfunktioner.
  • En closure-funktion, der er registreret med setGlobal(), holder de objekter, den indfanger, i live, så længe motoren har den globale variabel.