
# Funkce hostitele

Každý uzávěr nebo volatelný objekt PHP předaný enginu přes `setGlobal()` se změní na volatelnou funkci JS. Není třeba nic ručně registrovat, definovat schéma ani provádět marshalling — stačí předat funkci.

## Základy

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

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

Když `greet("world")` běží v JS:

1. Řetězec `"world"` se převede z JS na PHP.
2. Zavolá se uzávěr PHP s převedeným argumentem.
3. Návratová hodnota se převede z PHP zpět na JS.

Převod se řídí [pravidly pro převod hodnot](https://phasis.inline0.dev/cs/docs/interop/values). Argumenty se kopírují: pole nebo objekt JS se předá jako pole PHP a jeho změna v PHP nezmění hodnotu JS.

## Počet a parametry argumentů

Engine předá volatelné funkci JS argumenty pozičně a beze změny; její signaturu nezkoumá. Běžné uzávěry přebytečné argumenty ignorují a variadické parametry je shromáždí. Chybějící argumenty se nedoplňují: pokud JS předá méně argumentů, než volatelná funkce vyžaduje, PHP vyvolá `ArgumentCountError`. Nepovinným parametrům nastavte výchozí hodnoty.

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

## Přetypování

Argumenty se převádějí na PHP podle pravidel JS→PHP. Engine volá vaši funkci z kódu, který deklaruje `strict_types=1`, takže PHP striktně kontroluje typy skalárních parametrů a neprovádí automatickou konverzi: předání čísla typu JS parametru typu `string` je `TypeError`. Stejně jako každá PHP výjimka z hostitelské funkce se dostane k volajícímu `eval()`, nikoli do JS (viz níže).

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

Buď proveďte konverzi ručně uvnitř uzávěru (`(string) $value`), nebo použijte sjednocené typy (`int|string`).

## Předání výjimky zpět do JS

Běžné výjimky PHP vyvolané uvnitř hostitelské funkce se v současnosti nepřevádějí na chyby JS. Projdou všemi obslužnými rutinami JS `try` / `catch` a `.catch()` a dostanou se ke kódu PHP, který zavolal `eval()` nebo `call()`, a zachovají si původní třídu ([issue #20](https://github.com/inline0/phasis/issues/20)). Patří sem `\Exception`, `\RuntimeException`, `\DivisionByZeroError` a `\TypeError` i `\ArgumentCountError` PHP vyvolané při chybných argumentech.

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

Chcete-li vyvolat chybu, kterou může zachytit kód JS, vyvolejte některou z vlastních chybových tříd enginu: `Phasis\Exceptions\TypeError`, `RangeError` nebo `ReferenceError` se v JS projeví jako odpovídající typ chyby JS a každá jiná `Phasis\Exceptions\RuntimeError` jako prostá `Error`; v obou případech se zachová vaše zpráva.

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

## Vrácení strukturovaných hodnot

Návratová hodnota se převede podle pravidel PHP→JS. Chcete-li vrátit objektový literál JS, vraťte asociativní pole 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);
');
```

Chcete-li vrátit pole JS, vraťte sekvenční seznam 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
```

Vrácení objektu PHP, včetně příslibu PHP z ReactPHP, Amp nebo Guzzle, vede k JS `undefined` (viz [Převod hodnot](https://phasis.inline0.dev/cs/docs/interop/values)).

## Asynchronní hostitelské funkce

Hostitelské funkce jsou synchronní. Příslib PHP nebo jiný objekt thenable se nevyčká: převede se na `undefined`, takže `await hostFn()` vyřeší hodnotu `undefined`. Proveďte práci synchronně a vraťte výsledek; kód JS může volání stále `await`, čímž hodnotu zabalí do vyřešeného příslibu:

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

Konkrétně pro HTTP vestavěný `fetch()` již vrací skutečný příslib JS a lze jej směrovat přes vašeho klienta PHP pomocí [vlastního fetch transportu](https://phasis.inline0.dev/cs/docs/interop/fetch-transport).

## Poznámky k výkonu

Každé volání hostitelské funkce překračuje hranici PHP↔JS jednou pro argumenty a jednou pro návratovou hodnotu. Oba převody kopírují data, takže jejich náklady jsou O(size of value) — u primitiv jde o konstantní čas, u polí a objektů rostou úměrně jejich velikosti.

U často vykonávaných vnitřních smyček upřednostněte jedno volání, které vykoná více práce, před mnoha malými voláními. Nepředávejte sem a tam velká pole, když se mění jen několik položek.
