Перейти до вмісту

Спільні об’єкти

Передавання об’єкта PHP у JavaScript за посиланням — читання його властивостей, виклик його методів і відстеження змін у PHP — поки що не підтримується. Це відстежується в помилці №18. На цій сторінці описано, що відбувається зараз, і наведено робочі підходи.

Що відбувається зараз

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

Те саме стосується stdClass, сутностей і моделей, контейнерів служб, генераторів і ресурсів. Є два винятки — викликувані об’єкти:

  • Об’єкт із __invoke() стає функцією JS, що його викликає.
  • Викликуваний масив, як-от [$counter, 'increment'], стає функцією JS, що викликає цей метод.

Для надання об’єктів немає PhpBridge чи іншого класу-обгортки; src/Interop/ містить лише конвертер значень.

Надавайте методи через замикання

Замикання може захопити об’єкт, тож JS викликає функцію, а метод виконується на початковому екземплярі PHP:

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

Викликуваний масив дає той самий результат без замикання:

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

Об’єднуйте їх у масив

Асоціативний масив стає об’єктом JS, а замикання всередині нього — функціями. Так JS отримує об’єктоподібний API, методи якого працюють з екземпляром 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

JS бачить лише те, що ви помістили в масив, тому приватний стан, як-от $secret, залишається в PHP.

Поля даних копіюються під час запуску setGlobal(). У цьому прикладі user.name — це рядок у JS; присвоєння йому значення змінює копію JS, а не $user->name. Перенаправляйте всі операції запису через замикання.

Передавайте дані назовні, а результати — назад

Для об’єктів із даними конвертуйте їх у масив під час передавання всередину, а результат зчитуйте під час повернення:

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) або метод toArray() надає масив об’єкта. Потім застосуйте повернений масив до об’єкта, сутності чи моделі в PHP.

Застереження

  • Винятки, викинуті всередині цих замикань, наразі не перехоплюються в JS try / catch; вони доходять до місця виклику eval() (помилка №20). Див. розділ Функції хост-середовища.
  • Зареєстроване через setGlobal() замикання підтримує об’єкти, які воно захопило, доки рушій утримує глобальну змінну.