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:
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:
$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:
$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:
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:
$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ącegoeval()(issue #20). Zobacz Funkcje hosta. - Funkcja domknięcia zarejestrowana przez
setGlobal()utrzymuje przy życiu przechwycone obiekty tak długo, jak silnik przechowuje zmienną globalną.