Objetos compartilhados
Passar um objeto PHP por referência para JavaScript — ler suas propriedades, chamar seus métodos e ver as alterações em PHP — ainda não é compatível. Isso está registrado em problema #18. Esta página descreve o comportamento atual e os padrões que funcionam no lugar disso.
O que acontece atualmente
setGlobal() converte seu valor segundo as regras de Conversão de valores. Um objeto PHP que não é callable não tem conversão, então o global é 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')
O mesmo vale para stdClass, entidades e modelos, contêineres de serviços, geradores e recursos. Há duas exceções: objetos callable:
- Um objeto com
__invoke()torna-se uma função JS que o chama. - Um array callable como
[$counter, 'increment']torna-se uma função JS que chama esse método.
Não há PhpBridge nem outra classe wrapper para expor objetos; src/Interop/ contém apenas o conversor de valores.
Exponha métodos como closures
Uma closure pode capturar o objeto, então JS chama uma função e o método é executado na instância 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
Um array callable faz o mesmo sem uma closure:
$engine->setGlobal('increment', [$counter, 'increment']);
Agrupe-os em um array
Um array associativo torna-se um objeto JS, e as closures dentro dele tornam-se funções. Assim, JS recebe um objeto API com métodos que agem sobre a instância 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
Somente o que você colocar no array fica visível para JS, então o estado privado, como $secret, permanece em PHP.
Os campos de dados são cópias feitas quando setGlobal() é executado. No exemplo, user.name é uma string em JS; atribuir um valor a ela altera a cópia JS, não $user->name. Encaminhe todas as gravações por uma closure.
Envie dados e receba resultados
Para objetos que representam dados, converta-os em um array na entrada e leia o resultado na saída:
$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 um método toArray() fornece o array de um objeto. Em seguida, aplique o array retornado ao seu objeto, entidade ou modelo em PHP.
Observações
- Atualmente, exceções lançadas dentro dessas closures não podem ser capturadas por JS
try/catch; elas chegam ao chamador deeval()(problema #20). Consulte Funções hospedeiras. - Uma closure registrada com
setGlobal()mantém vivos os objetos que captura enquanto o mecanismo mantiver o global.