
# Funções do host

Qualquer closure ou função chamável PHP passada ao mecanismo por meio de `setGlobal()` torna-se uma função JS chamável. Não é necessário registrá-la manualmente nem definir um esquema ou fazer marshalling — basta passar a função.

## Noções básicas

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

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

Quando `greet("world")` é executado em JS:

1. A string `"world"` é convertida de JS para PHP.
2. A closure PHP é chamada com o argumento convertido.
3. O valor retornado é convertido de PHP para JS.

A conversão segue as [regras de conversão de valores](https://phasis.inline0.dev/pt/docs/interop/values). Os argumentos são cópias: um array ou objeto JS chega como array PHP, e alterá-lo em PHP não altera o valor JS.

## Aridade e parâmetros

O mecanismo passa os argumentos JS à função chamável na ordem posicional, sem modificá-los; ele não inspeciona a assinatura. Closures comuns ignoram argumentos extras, enquanto parâmetros variádicos os recebem. Argumentos ausentes não são preenchidos: se JS passar menos argumentos do que a função exige, PHP lança `ArgumentCountError`. Defina valores padrão para parâmetros opcionais.

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

## Conversão implícita de tipos

Os argumentos são convertidos para PHP usando as regras de JS→PHP. O mecanismo chama seu callable a partir de código que declara `strict_types=1`, então PHP verifica estritamente os tipos dos parâmetros escalares e não faz coerção: um número JS passado a um parâmetro `string` é um `TypeError`. Como qualquer exceção PHP de uma função hospedeira, ela chega ao chamador de `eval()` em vez de JS (veja abaixo).

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

Faça a coerção manualmente dentro da closure (`(string) $value`) ou use tipos de união (`int|string`).

## Propagando exceções de volta para JS

As exceções comuns de PHP lançadas dentro de uma função hospedeira **não** são convertidas em erros JS atualmente. Elas passam por todos os manipuladores JS `try` / `catch` e `.catch()` e chegam ao código PHP que chamou `eval()` ou `call()`, mantendo sua classe original ([problema #20](https://github.com/inline0/phasis/issues/20)). Isso inclui `\Exception`, `\RuntimeException`, `\DivisionByZeroError` e os erros `\TypeError` e `\ArgumentCountError` PHP lançados para argumentos inválidos.

```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 lançar um erro que o código JS possa capturar, lance uma das classes de erro do próprio mecanismo: `Phasis\Exceptions\TypeError`, `RangeError` ou `ReferenceError` chegam a JS como o tipo de erro JS correspondente, e qualquer outro `Phasis\Exceptions\RuntimeError` chega como um `Error` simples, ambos com sua mensagem.

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

## Retorno de valores complexos

O valor de retorno é convertido segundo as regras de PHP→JS. Para retornar um literal de objeto JS, retorne um 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);
');
```

Para retornar um array JS, retorne uma lista sequencial 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
```

Retornar um objeto PHP, incluindo uma promise PHP de ReactPHP, Amp ou Guzzle, resulta em JS `undefined` (veja [Conversão de valores](https://phasis.inline0.dev/pt/docs/interop/values)).

## Funções assíncronas do hospedeiro

As funções hospedeiras são síncronas. Uma promise PHP ou outro objeto thenable não é aguardado: ele é convertido em `undefined`, então `await hostFn()` é resolvido como `undefined`. Faça o trabalho de forma síncrona e retorne o resultado; o código JS ainda pode `await` a chamada, que envolve o valor em uma promise resolvida:

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

Especificamente para HTTP, o `fetch()` integrado já retorna uma promise JS real e pode ser encaminhado pelo seu próprio cliente PHP com um [transporte fetch personalizado](https://phasis.inline0.dev/pt/docs/interop/fetch-transport).

## Observações sobre desempenho

Cada chamada de função hospedeira cruza a fronteira PHP↔JS uma vez para os argumentos e outra para o valor de retorno. Ambas as conversões fazem cópias, portanto, têm custo O(size of value) — operações com valores primitivos têm tempo constante; arrays e objetos têm custo proporcional ao tamanho.

Em loops internos críticos para o desempenho, prefira uma chamada que faça mais trabalho a várias chamadas pequenas e evite passar arrays grandes de um lado para outro quando apenas alguns campos mudam.
