Vai al contenuto

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:

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')

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:

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

Un array richiamabile fa lo stesso senza una closure:

PHP
$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:

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:

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) 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 di eval() (problema n. 20). Vedi Funzioni host.
  • Una closure registrata con setGlobal() mantiene in vita gli oggetti che acquisisce finché il motore conserva il globale.