Общие объекты
Передача объекта PHP в JavaScript по ссылке — чтение его свойств, вызов методов и получение изменений в PHP — пока не поддерживается. Это отслеживается в проблеме №18. На этой странице описано, что происходит сейчас и какие шаблоны можно использовать вместо этого.
Что происходит сейчас
setGlobal() преобразует значение по правилам из раздела Преобразование значений. Объект PHP, который нельзя вызвать, не преобразуется, поэтому глобальная переменная равна 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')
То же относится к stdClass, сущностям и моделям, контейнерам сервисов, генераторам и ресурсам. Есть два исключения — вызываемые объекты:
- Объект с
__invoke()становится функцией JS, вызывающей его. - Вызываемый массив, например
[$counter, 'increment'], становится функцией JS, вызывающей этот метод.
Для предоставления объектов нет PhpBridge или другого класса-обёртки; src/Interop/ содержит только преобразователь значений.
Предоставление методов через замыкания
Замыкание может захватить объект, поэтому JS вызывает функцию, а метод выполняется для исходного экземпляра 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
Вызываемый массив делает то же самое без замыкания:
$engine->setGlobal('increment', [$counter, 'increment']);
Объединение в массив
Ассоциативный массив становится объектом JS, а содержащиеся в нём замыкания — функциями. Так у JS появляется объектоподобный API, методы которого работают с экземпляром 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 видно только то, что вы поместили в массив, поэтому приватное состояние, например $secret, остаётся в PHP.
Поля данных копируются при вызове setGlobal(). В примере user.name — строка в JS; присваивание ей значения меняет копию JS, а не $user->name. Все записи выполняйте через замыкание.
Передача данных и возврат результатов
Для объектов с данными преобразуйте их в массив при передаче и считайте результат при возврате:
$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) или метод toArray() возвращает массив для объекта. Затем примените возвращённый массив к объекту, сущности или модели в PHP.
Ограничения
- Исключения, выброшенные внутри этих замыканий, сейчас нельзя перехватить через JS
try/catch; они доходят до вызывающего кодаeval()(проблема №20). См. раздел Хост-функции. - Зарегистрированное с помощью
setGlobal()замыкание не позволяет объектам, которые оно захватило, удалиться, пока движок хранит глобальную переменную.