
# Fonctions hôtes

Toute fermeture ou fonction appelable PHP transmise au moteur via `setGlobal()` devient une fonction JS appelable. Aucune étape d’enregistrement manuel, aucun schéma ni marshalling : transmettez simplement la fonction.

## Bases

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

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

Lorsque `greet("world")` s’exécute dans JS :

1. La chaîne `"world"` est convertie de JS en PHP.
2. La fermeture PHP est appelée avec l’argument converti.
3. La valeur de retour est reconvertie de PHP en JS.

La conversion suit les [règles de conversion des valeurs](https://phasis.inline0.dev/fr/docs/interop/values). Les arguments sont des copies : un tableau ou objet JS devient un tableau PHP, et le modifier dans PHP ne change pas la valeur JS.

## Arity et paramètres

Le moteur transmet à la fonction appelable les arguments JS dans l’ordre, tels quels ; il n’inspecte pas la signature. Les arguments supplémentaires sont ignorés par les fermetures ordinaires et regroupés par les paramètres variadiques. Les arguments manquants ne sont pas ajoutés : si JS transmet moins d’arguments que la fonction n’en exige, PHP lance `ArgumentCountError`. Attribuez des valeurs par défaut aux paramètres facultatifs.

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

## Conversion implicite des types

Les arguments sont convertis en PHP selon les règles JS→PHP. Le moteur appelle votre fonction depuis du code qui déclare `strict_types=1` ; PHP vérifie donc strictement les types des paramètres scalaires et n’effectue aucune coercition : un nombre JS transmis à un paramètre `string` provoque une erreur `TypeError`. Comme toute exception PHP provenant d’une fonction hôte, elle remonte jusqu’à l’appelant de `eval()`, et non à JS (voir ci-dessous).

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

Vous pouvez effectuer une conversion manuelle dans la fermeture (`(string) $value`) ou utiliser des types union (`int|string`).

## Propager une exception vers JS

À ce jour, les exceptions PHP ordinaires lancées dans une fonction hôte ne sont **pas** converties en erreurs JS. Elles traversent chaque gestionnaire JS `try` / `catch` et `.catch()`, puis atteignent le code PHP qui a appelé `eval()` ou `call()`, avec leur classe d’origine ([problème #20](https://github.com/inline0/phasis/issues/20)). Cela inclut `\Exception`, `\RuntimeException`, `\DivisionByZeroError` ainsi que les exceptions PHP `\TypeError` et `\ArgumentCountError` générées en cas d’arguments incorrects.

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

Pour lancer une erreur que le code JS peut intercepter, lancez l’une des classes d’erreur du moteur : `Phasis\Exceptions\TypeError`, `RangeError` ou `ReferenceError` devient dans JS le type d’erreur JS correspondant ; toute autre `Phasis\Exceptions\RuntimeError` devient une simple `Error`, avec votre message dans les deux cas.

```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
    }
');
```

## Renvoyer des valeurs complexes

La valeur de retour est convertie selon les règles PHP→JS. Pour renvoyer un objet littéral JS, renvoyez un tableau associatif PHP :

```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);
');
```

Pour renvoyer un tableau JS, renvoyez une liste séquentielle PHP :

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

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

Renvoyer un objet PHP, y compris une promesse PHP issue de ReactPHP, Amp ou Guzzle, donne JS `undefined` (voir [Conversion des valeurs](https://phasis.inline0.dev/fr/docs/interop/values)).

## Fonctions hôtes asynchrones

Les fonctions hôtes sont synchrones. Une promesse PHP ou un autre objet thenable n’est pas attendu : il est converti en `undefined`, donc `await hostFn()` renvoie `undefined`. Effectuez le travail de façon synchrone et renvoyez le résultat ; le code JS peut tout de même `await` l’appel, ce qui encapsule la valeur dans une promesse déjà résolue :

```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();
');
```

Pour HTTP en particulier, le `fetch()` intégré renvoie déjà une véritable promesse JS et peut utiliser votre propre client PHP via un [transport fetch personnalisé](https://phasis.inline0.dev/fr/docs/interop/fetch-transport).

## Notes sur les performances

Chaque appel de fonction hôte franchit une fois la frontière PHP↔JS pour les arguments et une fois pour la valeur de retour. Les deux conversions effectuent une copie : leur coût est donc O(size of value) — constant pour les primitives et proportionnel à la taille des tableaux et des objets.

Dans les boucles internes très sollicitées, préférez un seul appel qui effectue davantage de travail à de nombreux petits appels, et évitez de faire passer de grands tableaux dans les deux sens si seuls quelques champs changent.
