Oggetti condivisi
Passare un oggetto PHP per riferimento a JavaScript — leggerne le proprietà, invocarne i metodi e vederne le modifiche in PHP — non è ancora supportato. La questione è tracciata in problema n. 18. Questa pagina descrive cosa succede oggi e quali schemi usare al suo posto.
Cosa succede oggi
setGlobal() converte il proprio valore secondo le regole descritte in Conversione dei valori. Un oggetto PHP non richiamabile non ha una conversione, quindi il valore globale è 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')
Lo stesso vale per stdClass, entità e modelli, contenitori di servizi, generatori e risorse. Fanno eccezione due tipi di oggetti richiamabili:
- Un oggetto con
__invoke()diventa una funzione JS che lo invoca. - Un array richiamabile come
[$counter, 'increment']diventa una funzione JS che invoca quel metodo.
Non esiste una classe wrapper PhpBridge o di altro tipo per esporre gli oggetti; src/Interop/ contiene solo il convertitore di valori.
Esporre metodi tramite closure
Una closure può acquisire l'oggetto, così JS invoca una funzione e il metodo opera sull'istanza PHP originale:
$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 array richiamabile fa lo stesso senza una closure:
$engine->setGlobal('increment', [$counter, 'increment']);
Raggrupparli in un array
Un array associativo diventa un oggetto JS e le closure al suo interno diventano funzioni. In questo modo JS ottiene un API con struttura a oggetto, i cui metodi operano sull'istanza 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
Solo ciò che inserisci nell'array è visibile a JS, quindi lo stato privato, come $secret, rimane in PHP.
I campi dati sono copie create quando viene eseguito setGlobal(). Nell'esempio, user.name è una stringa in JS; assegnarle un valore modifica la copia JS, non $user->name. Instrada ogni scrittura tramite una closure.
Passare i dati e ricevere i risultati
Per gli oggetti che rappresentano dati, convertili in un array all'ingresso e leggi il risultato all'uscita:
$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) o un metodo toArray() restituisce l'array dell'oggetto. Poi applica l'array restituito all'oggetto, all'entità o al modello in PHP.
Avvertenze
- Le eccezioni lanciate all'interno di queste closure al momento non possono essere intercettate da JS
try/catch; raggiungono il chiamante dieval()(problema n. 20). Vedi Funzioni host. - Una closure registrata con
setGlobal()mantiene in vita gli oggetti che acquisisce finché il motore conserva il globale.