Aller au contenu

Objets partagés

Le passage d’un objet PHP par référence à JavaScript — pour lire ses propriétés, appeler ses méthodes et voir les changements dans PHP — n’est pas encore pris en charge. Le suivi est disponible dans le problème #18. Cette page décrit le comportement actuel et les modèles qui fonctionnent à la place.

Comportement actuel

setGlobal() convertit sa valeur selon les règles de Conversion des valeurs. Un objet PHP non appelable n’a pas de conversion ; la variable globale vaut donc 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')

Cela vaut aussi pour stdClass, les entités et modèles, les conteneurs de services, les générateurs et les ressources. Deux exceptions existent pour les objets appelables :

  • Un objet doté de __invoke() devient une fonction JS qui l’appelle.
  • Un tableau appelable comme [$counter, 'increment'] devient une fonction JS qui appelle cette méthode.

Il n’existe pas de PhpBridge ni d’autre classe wrapper pour exposer des objets ; src/Interop/ ne contient que le convertisseur de valeurs.

Exposer des méthodes sous forme de fermetures

Une fermeture peut capturer l’objet ; JS appelle alors une fonction et la méthode s’exécute sur l’instance PHP d’origine :

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 tableau appelable fait la même chose sans fermeture :

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

Les regrouper dans un tableau

Un tableau associatif devient un objet JS et les fermetures qu’il contient deviennent des fonctions. JS obtient ainsi un API en forme d’objet, dont les méthodes agissent sur l’instance 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

Seuls les éléments du tableau sont visibles par JS ; l’état privé comme $secret reste donc dans PHP.

Les champs de données sont des copies créées à l’exécution de setGlobal(). Dans l’exemple, user.name est une chaîne dans JS ; la modifier change la copie JS, pas $user->name. Faites passer toutes les écritures par une fermeture.

Transmettre des données et récupérer les résultats

Pour les objets structurés en données, convertissez-les en tableau à l’entrée, puis lisez le résultat à la sortie :

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) ou une méthode toArray() fournit le tableau d’un objet. Appliquez ensuite le tableau obtenu à votre objet, entité ou modèle dans PHP.

Réserves

  • Les exceptions levées dans ces closures ne peuvent actuellement pas être interceptées par JS try / catch ; elles remontent jusqu’à l’appelant de eval() (issue #20). Voir Fonctions hôtes.
  • Une closure enregistrée avec setGlobal() conserve en vie les objets qu’elle capture tant que le moteur détient le global.