Спільні об’єкти
Передавання об’єкта 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()замикання підтримує об’єкти, які воно захопило, доки рушій утримує глобальну змінну.