Przejdź do treści

Obiekty współdzielone

Przekazywanie obiektu PHP do JavaScript przez referencję — odczytywanie jego właściwości, wywoływanie metod i obserwowanie zmian w PHP — nie jest jeszcze obsługiwane. Śledzimy to w zgłoszeniu #18. Ta strona opisuje obecne działanie i wzorce, których można użyć zamiast tego.

Obecne działanie

setGlobal() konwertuje wartość zgodnie z regułami opisanymi w Konwersja wartości. Obiekt PHP, który nie jest funkcją wywoływalną, nie ma konwersji, więc zmienna globalna ma wartość 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')

To samo dotyczy stdClass, encji i modeli, kontenerów usług, generatorów i zasobów. Wyjątkiem są dwa rodzaje obiektów wywoływalnych:

  • Obiekt z __invoke() staje się funkcją JS, która go wywołuje.
  • Tablica wywoływalna, taka jak [$counter, 'increment'], staje się funkcją JS wywołującą tę metodę.

Nie ma klasy PhpBridge ani innej klasy opakowującej do udostępniania obiektów; src/Interop/ zawiera wyłącznie konwerter wartości.

Udostępnianie metod jako domknięć

Domknięcie może przechwycić obiekt, więc JS wywołuje funkcję, a metoda działa na pierwotnej instancji 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

Tablica wywoływalna działa tak samo, bez domknięcia:

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

Grupowanie w tablicy

Tablica asocjacyjna staje się obiektem JS, a zawarte w niej domknięcia — funkcjami. Dzięki temu JS otrzymuje obiektowy API, którego metody działają na instancji 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

Dla JS widoczne są tylko elementy umieszczone w tablicy, więc prywatny stan, taki jak $secret, pozostaje w PHP.

Pola danych są kopiowane w chwili uruchomienia setGlobal(). W przykładzie user.name jest ciągiem w JS; przypisanie do niego zmienia kopię JS, a nie $user->name. Każdą zmianę wykonuj przez domknięcie.

Przekazywanie danych na zewnątrz i wyników z powrotem

W przypadku obiektów reprezentujących dane zamień je na tablicę przy przekazywaniu do silnika i odczytaj wynik przy odbieraniu:

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) lub metoda toArray() zwraca tablicę dla obiektu. Następnie przekaż zwróconą tablicę do obiektu, encji lub modelu w PHP.

Ograniczenia

  • Wyjątki zgłoszone wewnątrz tych funkcji domknięcia nie mogą być obecnie przechwycone przez JS try / catch; trafiają do wywołującego eval() (issue #20). Zobacz Funkcje hosta.
  • Funkcja domknięcia zarejestrowana przez setGlobal() utrzymuje przy życiu przechwycone obiekty tak długo, jak silnik przechowuje zmienną globalną.