
# Delte objekter

Det er endnu ikke understøttet at sende et PHP-objekt til JavaScript som reference, læse dets egenskaber, kalde dets metoder og se ændringerne i PHP. Det spores i [issue #18](https://github.com/inline0/phasis/issues/18). Denne side beskriver, hvad der sker i dag, og hvilke mønstre der virker i stedet.

## Hvad der sker i dag

`setGlobal()` konverterer værdien efter reglerne i [Værdikonvertering](https://phasis.inline0.dev/da/docs/interop/values). Et PHP-objekt, der ikke kan kaldes, har ingen konvertering, så den globale variabel bliver `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')
```

Det samme gælder `stdClass`, entiteter og modeller, servicecontainere, generatorer og ressourcer. To undtagelser er objekter, der kan kaldes:

- Et objekt med `__invoke()` bliver til en JS-funktion, der kalder det.
- Et callable array som `[$counter, 'increment']` bliver til en JS-funktion, der kalder den pågældende metode.

Der findes ingen `PhpBridge` eller anden wrapperklasse til eksponering af objekter; `src/Interop/` indeholder kun værdikonverteren.

## Eksponér metoder som closure-funktioner

En closure-funktion kan indfange objektet, så JS kalder en funktion, og metoden kører på den oprindelige PHP-instans:

```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
```

Et callable array gør det samme uden en closure-funktion:

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

## Saml dem i et array

Et associativt array bliver til et JS-objekt, og closure-funktionerne i det bliver til funktioner. Dermed får JS et API med objektform, hvis metoder arbejder på PHP-instansen:

```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
```

Kun det, du lægger i arrayet, er synligt for JS, så privat tilstand som `$secret` forbliver i PHP.

Datafelterne er kopier, der tages, når `setGlobal()` kører. I eksemplet er `user.name` en streng i JS; hvis du tildeler den en værdi, ændres JS-kopien, ikke `$user->name`. Send alle skrivninger gennem en closure-funktion.

## Send data ud, og resultater tilbage

For dataformede objekter skal du konvertere til et array på vej ind og læse resultatet på vej ud:

```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)` eller en `toArray()`-metode giver dig arrayet for et objekt. Anvend derefter det returnerede array på dit objekt, din entitet eller din model i PHP.

## Forbehold

- Undtagelser, der kastes inde i disse closure-funktioner, kan i øjeblikket ikke fanges af JS `try` / `catch`; de når den, der kaldte `eval()` ([issue #20](https://github.com/inline0/phasis/issues/20)). Se [Værtsfunktioner](https://phasis.inline0.dev/da/docs/interop/host-functions#throwing-back-into-js).
- En closure-funktion, der er registreret med `setGlobal()`, holder de objekter, den indfanger, i live, så længe motoren har den globale variabel.
