
# Funzioni host

Qualsiasi closure o callable PHP fornito al motore tramite `setGlobal()` diventa una funzione callable JS. Non servono registrazioni manuali, schemi o marshalling: basta passare il callable.

## Nozioni di base

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

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

Quando `greet("world")` viene eseguito in JS:

1. La stringa `"world"` viene convertita da JS a PHP.
2. La closure PHP viene invocata con l'argomento convertito.
3. Il valore restituito viene riconvertito da PHP a JS.

La conversione segue le [regole di conversione dei valori](https://phasis.inline0.dev/it/docs/interop/values). Gli argomenti vengono copiati: un array o un oggetto JS arriva come array PHP e le modifiche apportate in PHP non cambiano il valore JS.

## Numero di argomenti e parametri

Il motore passa al callable gli argomenti JS in ordine posizionale, così come sono, senza esaminare la firma. Le closure ordinarie ignorano gli argomenti aggiuntivi e i parametri variadici li raccolgono. Gli argomenti mancanti non vengono inseriti automaticamente: se JS passa meno argomenti del necessario, PHP genera `ArgumentCountError`. Assegnate valori predefiniti ai parametri facoltativi.

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

## Conversione implicita dei tipi

Gli argomenti vengono convertiti in PHP secondo le regole JS→PHP. Il motore invoca la funzione richiamabile da codice che dichiara `strict_types=1`, quindi PHP verifica rigorosamente i tipi dei parametri scalari e non esegue conversioni implicite: un valore numerico JS passato a un parametro `string` è un `TypeError`. Come qualsiasi eccezione PHP proveniente da una funzione host, raggiunge il chiamante di `eval()` invece di JS (vedi sotto).

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

Esegui manualmente la conversione nella closure (`(string) $value`) oppure usa tipi unione (`int|string`).

## Rilanciare in JS

Le normali eccezioni PHP lanciate all'interno di una funzione host non vengono attualmente convertite in errori JS. Attraversano tutti gli handler JS `try` / `catch` e `.catch()`, quindi raggiungono il codice PHP che ha chiamato `eval()` o `call()`, mantenendo la classe originale ([problema n. 20](https://github.com/inline0/phasis/issues/20)). Tra queste ci sono `\Exception`, `\RuntimeException`, `\DivisionByZeroError` e gli errori `\TypeError` e `\ArgumentCountError` PHP generati per argomenti non validi.

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

Per generare un errore che il codice JS possa intercettare, lancia una delle classi di errore proprie del motore: `Phasis\Exceptions\TypeError`, `RangeError` o `ReferenceError` diventano in JS il tipo di errore JS corrispondente; qualsiasi altro `Phasis\Exceptions\RuntimeError` diventa un semplice `Error`, ciascuno con il tuo messaggio.

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

## Restituire valori complessi

Il valore restituito viene convertito secondo le regole PHP→JS. Per restituire un oggetto letterale JS, restituisci un array associativo 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);
');
```

Per restituire un array JS, restituisci un elenco sequenziale 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
```

Restituire un oggetto PHP, compresa una promise PHP da ReactPHP, Amp o Guzzle, produce JS `undefined` (vedi [Conversione dei valori](https://phasis.inline0.dev/it/docs/interop/values)).

## Funzioni host asincrone

Le funzioni host sono sincrone. Una promise PHP o un altro oggetto thenable non viene atteso: viene convertito in `undefined`, quindi `await hostFn()` si risolve in `undefined`. Esegui il lavoro in modo sincrono e restituisci il risultato; il codice JS può comunque `await` la chiamata, che racchiude il valore in una promise risolta:

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

In particolare, per HTTP, il metodo integrato `fetch()` restituisce già una vera promise JS e può essere instradato tramite il tuo client PHP con un [trasporto fetch personalizzato](https://phasis.inline0.dev/it/docs/interop/fetch-transport).

## Note sulle prestazioni

Ogni chiamata a una funzione host attraversa una volta il confine PHP↔JS per gli argomenti e una volta per il valore restituito. Entrambe le conversioni copiano i dati, quindi hanno costo O(size of value): le primitive richiedono tempo costante, gli array e gli oggetti un tempo proporzionale alla loro dimensione.

Nei cicli interni più frequenti, preferisci una singola chiamata che svolga più lavoro a molte chiamate piccole ed evita di passare grandi array avanti e indietro quando cambiano solo pochi campi.
