Obiecte partajate
Transmiterea unui obiect PHP către JavaScript prin referință — citirea proprietăților, apelarea metodelor și observarea modificărilor în PHP — nu este încă acceptată. Problema este urmărită în problema nr. 18. Această pagină descrie comportamentul actual și tiparele alternative care funcționează.
Comportamentul actual
setGlobal() își convertește valoarea conform regulilor din Conversia valorilor. Un obiect PHP care nu este apelabil nu are o conversie, astfel că valoarea globală este 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')
Același lucru se aplică pentru stdClass, entități și modele, containere de servicii, generatoare și resurse. Există două excepții: obiectele apelabile:
- Un obiect cu
__invoke()devine o funcție JS care îl apelează. - Un tablou apelabil, precum
[$counter, 'increment'], devine o funcție JS care apelează acea metodă.
Nu există PhpBridge sau altă clasă wrapper pentru expunerea obiectelor; src/Interop/ conține doar convertorul de valori.
Expunerea metodelor ca funcții closure
O funcție closure poate captura obiectul, astfel că JS apelează o funcție, iar metoda rulează pe instanța PHP originală:
$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
Un tablou apelabil face același lucru fără o funcție closure:
$engine->setGlobal('increment', [$counter, 'increment']);
Gruparea într-un tablou
Un tablou asociativ devine un obiect JS, iar funcțiile closure din interior devin funcții. Astfel, JS primește un obiect API ale cărui metode operează asupra instanței 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
Doar elementele incluse în tablou sunt vizibile pentru JS, astfel că starea privată, precum $secret, rămâne în PHP.
Câmpurile de date sunt copii preluate când rulează setGlobal(). În exemplu, user.name este un șir în JS; atribuirea unei valori îi modifică copia JS, nu pe $user->name. Efectuați toate scrierile printr-o funcție closure.
Transmiterea datelor și returnarea rezultatelor
Pentru obiecte structurate ca date, convertiți la intrare într-un tablou și citiți rezultatul la ieșire:
$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) sau o metodă toArray() vă oferă tabloul unui obiect. Apoi aplicați tabloul returnat obiectului, entității sau modelului în PHP.
Observații
- Excepțiile aruncate în interiorul acestor funcții anonime nu pot fi capturate momentan de JS
try/catch; acestea ajung la apelantul luieval()(problema #20). Consultați Funcții gazdă. - O funcție anonimă înregistrată cu
setGlobal()menține în viață obiectele pe care le capturează cât timp motorul păstrează globalul.