
# Funkcie hostiteľa

Každá uzávierka alebo volateľný objekt PHP odovzdaný enginu cez `setGlobal()` sa stane volateľnou funkciou JS. Netreba ju ručne registrovať, definovať schému ani maršalovať — stačí odovzdať volateľný objekt.

## Základy

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

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

Keď sa `greet("world")` spustí v JS:

1. Reťazec `"world"` sa prevedie z JS na PHP.
2. Uzávierka PHP sa zavolá s prevedeným argumentom.
3. Návratová hodnota sa prevedie z PHP späť na JS.

Prevod sa riadi [pravidlami prevodu hodnôt](https://phasis.inline0.dev/sk/docs/interop/values). Argumenty sú kópie: pole alebo objekt JS sa odovzdá ako pole PHP a zmeny v PHP nezmenia hodnotu JS.

## Počet argumentov a parametre

Engine odovzdáva volateľnému objektu argumenty JS pozične a bez zmien; signatúru nekontroluje. Bežné uzávierky ignorujú nadbytočné argumenty a variadické parametre ich zhromažďujú. Chýbajúce argumenty sa nedopĺňajú: ak JS odovzdá menej argumentov, než volateľný objekt vyžaduje, PHP vyvolá `ArgumentCountError`. Voliteľným parametrom nastavte predvolené 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
```

## Konverzia typov

Argumenty sa prevádzajú na PHP podľa pravidiel JS→PHP. Engine volá váš volateľný objekt z kódu deklarujúceho `strict_types=1`, preto PHP striktne kontroluje skalárne typy parametrov a nevykonáva konverziu: číslo JS odovzdané parametru `string` spôsobí `TypeError`. Rovnako ako každá výnimka PHP z funkcie hostiteľa sa dostane k volajúcemu `eval()`, nie do JS (pozrite nižšie).

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

Typ môžete previesť ručne vnútri uzávierky (`(string) $value`) alebo použiť zjednotené typy (`int|string`).

## Vyvolanie výnimky späť do JS

Bežné výnimky PHP vyvolané vo funkcii hostiteľa sa dnes **neprevádzajú** na chyby JS. Prechádzajú cez každý JS `try` / `catch` a obslužnú rutinu `.catch()` až ku kódu PHP, ktorý zavolal `eval()` alebo `call()`, pričom si zachovávajú pôvodnú triedu ([chyba č. 20](https://github.com/inline0/phasis/issues/20)). Patria sem `\Exception`, `\RuntimeException`, `\DivisionByZeroError` a chyby PHP `\TypeError` a `\ArgumentCountError` vyvolané pri nesprávnych argumentoch.

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

Ak chcete vyvolať chybu, ktorú dokáže zachytiť kód JS, vyvolajte jednu z vlastných tried chýb enginu: `Phasis\Exceptions\TypeError`, `RangeError` alebo `ReferenceError` sa v JS prejavia ako zodpovedajúci typ chyby JS; každá iná `Phasis\Exceptions\RuntimeError` sa prejaví ako obyčajná `Error`, v oboch prípadoch s vašou správou.

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

## Vracanie komplexných hodnôt

Návratová hodnota sa prevedie podľa pravidiel PHP→JS. Objekt JS vytvorený priamo v kóde vrátite asociatívnym poľom 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);
');
```

Pole JS vrátite ako postupný zoznam 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átením objektu PHP vrátane prísľubu PHP z ReactPHP, Amp alebo Guzzle získate JS `undefined` (pozrite [Prevod hodnôt](https://phasis.inline0.dev/sk/docs/interop/values)).

## Asynchrónne funkcie hostiteľa

Funkcie hostiteľa sú synchrónne. Na prísľub PHP ani iný objekt typu thenable sa nečaká: prevedie sa na `undefined`, takže `await hostFn()` sa vyrieši na `undefined`. Prácu vykonajte synchrónne a vráťte výsledok; kód JS môže volanie aj tak vykonať pomocou `await`, čím sa hodnota obalí do vyriešeného prísľubu:

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

Špecificky pre HTTP vstavaný `fetch()` už vracia skutočný prísľub JS a možno ho presmerovať cez vlastného klienta PHP pomocou [vlastného transportu fetch](https://phasis.inline0.dev/sk/docs/interop/fetch-transport).

## Poznámky k výkonu

Každé volanie funkcie hostiteľa prekračuje hranicu PHP↔JS raz pri argumentoch a raz pri návratovej hodnote. Pri oboch prevodoch sa údaje kopírujú, takže ich náklady sú O(size of value) — primitívne hodnoty sa spracúvajú v konštantnom čase, pri poliach a objektoch náklady rastú úmerne ich veľkosti.

V často vykonávaných vnútorných slučkách uprednostnite jedno volanie, ktoré urobí viac práce, pred množstvom malých volaní. Vyhnite sa aj opakovanému odovzdávaniu veľkých polí, keď sa mení iba niekoľko položiek.
