Objetos compartidos
Aún no se admite pasar un objeto PHP por referencia a JavaScript —leer sus propiedades, llamar a sus métodos y ver los cambios en PHP. El tema se sigue en la incidencia n.º 18. Esta página describe el comportamiento actual y los patrones que sí funcionan.
Qué ocurre actualmente
setGlobal() convierte su valor según las reglas de Conversión de valores. Un objeto PHP que no se puede invocar no tiene conversión, así que la variable global es 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 mismo se aplica a stdClass, entidades y modelos, contenedores de servicios, generadores y recursos. Hay dos excepciones: los objetos invocables:
- Un objeto con
__invoke()se convierte en una función JS que lo llama. - Un array invocable, como
[$counter, 'increment'], se convierte en una función JS que llama a ese método.
No existe una clase contenedora PhpBridge ni otra para exponer objetos; src/Interop/ solo contiene el conversor de valores.
Exponer métodos como cierres
Un cierre puede capturar el objeto; así, JS llama a una función y el método se ejecuta en la instancia 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 array invocable hace lo mismo sin un cierre:
$engine->setGlobal('increment', [$counter, 'increment']);
Agruparlos en un array
Un array asociativo se convierte en un objeto JS y los cierres que contiene se convierten en funciones. Así, JS obtiene un API con forma de objeto cuyos métodos actúan sobre la instancia 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
JS solo puede ver lo que incluyas en el array, así que el estado privado, como $secret, permanece en PHP.
Los campos de datos son copias tomadas cuando se ejecuta setGlobal(). En el ejemplo, user.name es una cadena en JS; asignarle otro valor cambia la copia de JS, no $user->name. Canaliza todas las escrituras mediante un cierre.
Pasar datos y devolver resultados
Para objetos con estructura de datos, conviértalos en un array al introducirlos y lea el resultado al extraerlo:
$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 método toArray() le proporciona el array de un objeto. Después, aplique el array devuelto a su objeto, entidad o modelo en PHP.
Advertencias
- Las excepciones lanzadas dentro de estos cierres actualmente no pueden ser capturadas por JS
try/catch; llegan al llamador deeval()(incidencia #20). Consulte Funciones del host. - Un cierre registrado con
setGlobal()mantiene vivos los objetos que captura mientras el motor conserve el global.