
# Funciones del host

Cualquier cierre o función invocable PHP que se pase al motor mediante `setGlobal()` se convierte en una función invocable JS. No hace falta registrarla manualmente, definir un esquema ni serializarla: basta con pasar la función.

## Conceptos básicos

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

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

Cuando `greet("world")` se ejecuta en JS:

1. La cadena `"world"` se convierte de JS a PHP.
2. Se invoca el cierre PHP con el argumento convertido.
3. El valor devuelto se convierte de PHP a JS.

La conversión sigue las [reglas de conversión de valores](https://phasis.inline0.dev/es/docs/interop/values). Los argumentos son copias: un array o un objeto JS llega como un array PHP nuevo, y modificarlo en PHP no cambia el valor de JS.

## Aridad y parámetros

El motor pasa los argumentos de JS a la función invocable por posición y tal como están; no inspecciona la firma. Los cierres ordinarios ignoran los argumentos adicionales y los parámetros variádicos los recopilan. No se completan los argumentos que falten: si JS pasa menos argumentos de los que exige la función invocable, PHP lanza `ArgumentCountError`. Asigna valores predeterminados a los parámetros opcionales.

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

## Conversión de tipos

Los argumentos se convierten a PHP según las reglas de JS→PHP. El motor llama a tu función invocable desde código que declara `strict_types=1`, por lo que PHP comprueba estrictamente los tipos de los parámetros escalares y no convierte los valores: pasar un número JS a un parámetro `string` es un `TypeError`. Como cualquier excepción PHP de una función del host, llega a quien llama a `eval()`, no a JS (véase más abajo).

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

Convierte el valor manualmente dentro del cierre (`(string) $value`) o usa tipos unión (`int|string`).

## Propagar excepciones a JS

Las excepciones ordinarias de PHP lanzadas dentro de una función del host **no** se convierten en errores JS actualmente. Atraviesan cada manejador `try` / `catch` y `.catch()` de JS, y llegan al código PHP que llamó a `eval()` o `call()`, conservando su clase original ([incidencia n.º 20](https://github.com/inline0/phasis/issues/20)). Esto incluye `\Exception`, `\RuntimeException`, `\DivisionByZeroError` y las excepciones que PHP `\TypeError` y `\ArgumentCountError` lanzan para argumentos incorrectos.

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

Para lanzar un error que pueda capturar el código JS, lanza una de las clases de error propias del motor: `Phasis\Exceptions\TypeError`, `RangeError` o `ReferenceError` llegan a JS como el tipo de error JS correspondiente; cualquier otro `Phasis\Exceptions\RuntimeError` llega como un `Error` simple, en ambos casos con tu mensaje.

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

## Devolver valores complejos

El valor devuelto se convierte según las reglas de PHP→JS. Para devolver un objeto literal JS, devuelve un array asociativo 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);
');
```

Para devolver un array JS, devuelve una lista secuencial 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
```

Devolver un objeto PHP, incluida una promesa PHP de ReactPHP, Amp o Guzzle, produce JS `undefined` (consulta [Conversión de valores](https://phasis.inline0.dev/es/docs/interop/values)).

## Funciones asíncronas del host

Las funciones del host son síncronas. Una promesa PHP u otro objeto thenable no se espera: se convierte en `undefined`, así que `await hostFn()` se resuelve como `undefined`. Realiza el trabajo de forma síncrona y devuelve el resultado; el código JS puede aplicar `await` a la llamada, que envuelve el valor en una promesa resuelta:

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

Para HTTP en concreto, `fetch()` integrado ya devuelve una promesa JS real y puede dirigirse a tu cliente PHP mediante un [transporte fetch personalizado](https://phasis.inline0.dev/es/docs/interop/fetch-transport).

## Notas de rendimiento

Cada llamada a una función del host cruza una vez el límite PHP↔JS para los argumentos y otra para el valor devuelto. Ambas conversiones hacen copias, por lo que su coste es O(size of value): constante para valores primitivos y proporcional al tamaño para arrays y objetos.

En bucles internos muy frecuentes, es mejor hacer más trabajo en una sola llamada que muchas llamadas pequeñas. Evita pasar arrays grandes de un lado a otro cuando solo cambien unos pocos campos.
