
# Hostfuncties

Elke PHP-closure of callable die via `setGlobal()` aan de engine wordt doorgegeven, wordt een aanroepbare JS-functie. Je hoeft niets handmatig te registreren, geen schema op te geven of gegevens te marshallen — geef de callable gewoon door.

## Basis

```php
$engine->setGlobal('greet', function (string $name): string {
    return "Hello, $name!";
});

echo $engine->eval('greet("world")');
// Hello, world!
```

Wanneer `greet("world")` in JS wordt uitgevoerd:

1. De tekenreeks `"world"` wordt omgezet van JS naar PHP.
2. De PHP-closure wordt aangeroepen met het omgezette argument.
3. De retourwaarde wordt terug omgezet van PHP naar JS.

De conversie volgt de [regels voor waardeconversie](https://phasis.inline0.dev/nl/docs/interop/values). Argumenten zijn kopieën: een JS-array of -object komt aan als PHP-array. Wijzigingen eraan in PHP veranderen de JS-waarde niet.

## Aantal en parameters

De engine geeft de JS-argumenten positioneel en ongewijzigd door aan de callable; de functiehandtekening wordt niet gecontroleerd. Gewone closures negeren extra argumenten, terwijl variadische parameters ze verzamelen. Ontbrekende argumenten worden niet aangevuld: als JS minder argumenten doorgeeft dan de callable vereist, gooit PHP `ArgumentCountError`. Geef optionele parameters standaardwaarden.

```php
$engine->setGlobal('format', function (string $template, ...$args): string {
    return vsprintf($template, $args);
});

echo $engine->eval('format("%s is %d years old", "Ada", 36)');
// Ada is 36 years old
```

## Typeconversie

Argumenten worden volgens de regels JS→PHP omgezet naar PHP. De engine roept je callable aan vanuit code die `strict_types=1` declareert, dus PHP controleert scalare parametertypen strikt en voert geen typeconversie uit: een JS getal dat aan een parameter van het type `string` wordt doorgegeven, is een `TypeError`. Net als elke PHP-uitzondering van een hostfunctie bereikt deze de aanroeper van `eval()`, in plaats van JS (zie hieronder).

```php
$engine->setGlobal('len', fn(string $s): int => strlen($s));

$engine->eval('len(42)');  // PHP TypeError: Argument #1 ($s) must be of type string, int given
```

Voer de typeconversie handmatig uit binnen de closure (`(string) $value`) of gebruik uniontypen (`int|string`).

## Teruggooien naar JS

Gewone uitzonderingen PHP die binnen een hostfunctie worden gegenereerd, worden momenteel **niet** omgezet in JS-fouten. Ze gaan door elke JS `try` / `catch`- en `.catch()`-handler en bereiken de PHP-code die `eval()` of `call()` heeft aangeroepen, met hun oorspronkelijke klasse ([probleem #20](https://github.com/inline0/phasis/issues/20)). Dat geldt ook voor `\Exception`, `\RuntimeException`, `\DivisionByZeroError` en de `\TypeError`- en `\ArgumentCountError`-PHP-fouten bij ongeldige argumenten.

```php
$engine->setGlobal('divide', function (int $a, int $b): int {
    if ($b === 0) {
        throw new \DivisionByZeroError('cannot divide by zero');
    }
    return intdiv($a, $b);
});

try {
    $engine->eval('
        try { divide(10, 0); } catch (e) { /* never runs */ }
    ');
} catch (\DivisionByZeroError $e) {
    echo $e->getMessage();  // cannot divide by zero
}
```

Als je een fout wilt gooien die JS-code kan opvangen, gooi dan een van de eigen foutklassen van de engine: `Phasis\Exceptions\TypeError`, `RangeError` of `ReferenceError` komen in JS aan als het overeenkomstige fouttype van JS, en elke andere `Phasis\Exceptions\RuntimeError` komt aan als een gewone `Error`, telkens met jouw bericht.

```php
use Phasis\Exceptions\RangeError;

$engine->setGlobal('divide', function (int $a, int $b): int {
    if ($b === 0) {
        throw new RangeError('cannot divide by zero');
    }
    return intdiv($a, $b);
});

$engine->eval('
    try {
        divide(10, 0);
    } catch (e) {
        console.log(e.name, e.message);  // RangeError cannot divide by zero
    }
');
```

## Rijke waarden retourneren

De retourwaarde wordt omgezet volgens de regels PHP→JS. Als je een objectliteral van het type JS wilt retourneren, geef dan een associatieve array van PHP terug:

```php
$engine->setGlobal('lookup', function (string $key): array {
    return ['key' => $key, 'value' => 42, 'meta' => ['ts' => time()]];
});

$engine->eval('
    const r = lookup("answer");
    console.log(r.value, r.meta.ts);
');
```

Als je een array van JS wilt retourneren, geef dan een opeenvolgende lijst van PHP terug:

```php
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));

$engine->eval('range(1, 5).reduce((a, b) => a + b)');  // 15
```

Een object van PHP retourneren, waaronder een promise van ReactPHP, Amp of Guzzle van PHP, levert JS `undefined` op (zie [Waardeconversie](https://phasis.inline0.dev/nl/docs/interop/values)).

## Asynchrone hostfuncties

Hostfuncties zijn synchroon. Er wordt niet gewacht op een promise van PHP of een ander thenable-object: dit wordt omgezet naar `undefined`, waardoor `await hostFn()` wordt opgelost naar `undefined`. Voer het werk synchroon uit en retourneer het resultaat; JS-code kan de aanroep nog steeds `await`, waardoor de waarde in een opgeloste promise wordt verpakt:

```php
$engine->setGlobal('loadBody', function (string $url): string {
    return file_get_contents($url);
});

$engine->eval('
    async function main() {
        const body = await loadBody("https://example.com");
        console.log(body.length);
    }
    main();
');
```

Voor HTTP specifiek retourneert de ingebouwde `fetch()` al een echte promise van JS en kan deze via je eigen PHP-client worden gerouteerd met een [aangepast fetch-transport](https://phasis.inline0.dev/nl/docs/interop/fetch-transport).

## Opmerkingen over prestaties

Elke aanroep van een hostfunctie passeert de grens tussen PHP en JS eenmaal voor de argumenten en eenmaal voor de retourwaarde. Beide conversies maken een kopie, dus ze zijn O(size of value) — primitieven nemen constante tijd in beslag; arrays en objecten kosten tijd evenredig aan hun omvang.

Geef in hete binnenste lussen de voorkeur aan één aanroep die meer werk doet boven veel kleine aanroepen, en vermijd het heen en weer sturen van grote arrays wanneer slechts enkele velden veranderen.
