Hoppa till innehåll

Delade objekt

Det går inte ännu att skicka ett objekt av typen PHP till JavaScript som referens — läsa dess egenskaper, anropa dess metoder och se ändringarna i PHP. Detta följs i ärende nr 18. Den här sidan beskriver vad som händer i dag och vilka mönster som fungerar i stället.

Vad som händer i dag

setGlobal() konverterar sitt värde enligt reglerna i Värdekonvertering. Ett objekt av typen PHP som inte är anropsbart kan inte konverteras, så den globala variabeln blir 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')

Samma sak gäller stdClass, entiteter och modeller, tjänstebehållare, generatorer och resurser. Det finns två undantag: anropsbara objekt:

  • Ett objekt med __invoke() blir en funktion av typen JS som anropar det.
  • En anropsbar array, till exempel [$counter, 'increment'], blir en funktion av typen JS som anropar metoden.

Det finns ingen PhpBridge eller annan omslagsklass för att exponera objekt; src/Interop/ innehåller bara värdekonverteraren.

Exponera metoder som slutningar

En slutning kan fånga objektet, så JS anropar en funktion och metoden körs på den ursprungliga instansen av typen 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

En anropsbar array gör samma sak utan en slutning:

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

Samla dem i en array

En associativ array blir ett objekt av typen JS och slutningar i den blir funktioner. Då får JS ett objektformat API vars metoder arbetar på instansen av typen 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

Endast det du lägger i arrayen är synligt för JS, så privat tillstånd som $secret stannar i PHP.

Datafälten kopieras när setGlobal() körs. I exemplet är user.name en sträng i JS; om du tilldelar ett nytt värde ändras kopian i JS, inte $user->name. Låt en slutning hantera varje skrivning.

Skicka ut data och ta emot resultat

För objekt formade som data konverterar du till en array på vägen in och läser resultatet på vägen ut:

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 metod för toArray() ger dig arrayen för ett objekt. Applicera sedan den returnerade arrayen på objektet, entiteten eller modellen i PHP.

Förbehåll

  • Undantag som kastas inuti dessa closures kan för närvarande inte fångas av JS try / catch; de når anroparen av eval() (problem nr 20). Se Värdfunktioner.
  • En closure som registreras med setGlobal() håller objekten den fångar vid liv så länge motorn behåller globalen.