
# Gazdafüggvények

A motorhoz a `setGlobal()` segítségével átadott bármely PHP closure vagy hívható elem meghívható JS függvénnyé válik. Nincs szükség kézi regisztrációra, sémára vagy marshallingra — egyszerűen adja át a hívható elemet.

## Alapok

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

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

Amikor a `greet("world")` fut a JS-ben:

1. A `"world"` karakterlánc JS formátumról PHP formátumra alakul.
2. A PHP closure a konvertált argumentummal meghívódik.
3. A visszatérési érték PHP formátumról visszaalakul JS formátumra.

Az átalakítás az [értékátalakítási szabályokat](https://phasis.inline0.dev/hu/docs/interop/values) követi. Az argumentumok másolatok: egy JS tömb vagy objektum PHP tömbként érkezik meg, és a PHP-ben végzett módosítása nem változtatja meg a JS értéket.

## Argumentumok száma és paraméterek

A motor sorrendben, változatlanul adja át a hívható elemnek a JS argumentumokat; az aláírást nem vizsgálja. A szokásos closure-ök figyelmen kívül hagyják a további argumentumokat, a variadikus paraméterek pedig összegyűjtik őket. A hiányzó argumentumok nem kapnak automatikusan értéket: ha a JS kevesebb argumentumot ad át, mint amennyit a hívható elem megkövetel, a PHP `ArgumentCountError` hibát dob. Az opcionális paraméterekhez adjon meg alapértelmezett értéket.

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

## Típusösszefésülés

Az argumentumok a PHP típusra konvertálódnak a JS→PHP szabályai szerint. A motor olyan kódból hívja meg a hívható elemet, amely `strict_types=1`-t deklarál, ezért a PHP szigorúan ellenőrzi a skalár paramétertípusokat, és nem végez típuskonverziót: egy JS szám átadása egy `string` paraméternek `TypeError`. Mint bármely, gazdafüggvényből származó PHP kivétel, ez is a `eval()` hívójához jut el, nem pedig a JS-hez (lásd alább).

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

Végezze el kézzel a konverziót a closure-ön belül (`(string) $value`), vagy használjon union típusokat (`int|string`).

## Visszadobás JS-be

A gazdafüggvényben dobott szokásos PHP kivételek ma **nem** alakulnak át JS hibákká. Minden JS `try` / `catch` és `.catch()` kezelőn változatlanul haladnak át, majd eljutnak az PHP kódhoz, amely meghívta a `eval()` vagy a `call()` függvényt, eredeti osztályukkal ([issue #20](https://github.com/inline0/phasis/issues/20)). Ide tartozik a `\Exception`, a `\RuntimeException`, a `\DivisionByZeroError`, valamint a `\TypeError` és a `\ArgumentCountError` PHP által érvénytelen argumentumok esetén kiváltott hibák.

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

Ha olyan hibát szeretne kiváltani, amelyet a JS kód el tud kapni, dobja a motor saját hibaosztályainak egyikét: a `Phasis\Exceptions\TypeError`, a `RangeError` vagy a `ReferenceError` a megfelelő JS hibatípusként érkezik meg a JS-be, bármely más `Phasis\Exceptions\RuntimeError` pedig egyszerű `Error`-ként, mindegyik az Ön üzenetével.

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

## Összetett értékek visszaadása

A visszatérési érték a PHP→JS szabályai szerint konvertálódik. JS objektumliterál visszaadásához adjon vissza PHP asszociatív tömböt:

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

JS tömb visszaadásához adjon vissza egy soros PHP listát:

```php
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));

$engine->eval('range(1, 5).reduce((a, b) => a + b)');  // 15
```

Ha ReactPHP, Amp vagy Guzzle egy PHP objektumot ad vissza, akár egy PHP Promise-t is, az JS `undefined`-t eredményez (lásd: [Értékek konvertálása](https://phasis.inline0.dev/hu/docs/interop/values)).

## Aszinkron gazdafüggvények

A gazdafüggvények szinkron módon működnek. A PHP promise-okat és más thenable objektumokat a rendszer nem várja meg: `undefined`-re konvertálja őket, ezért a `await hostFn()` eredménye `undefined` lesz. Végezze el szinkron módon a műveletet, és adja vissza az eredményt; a JS kód továbbra is használhatja az `await`-t a híváshoz, amely a visszatérési értéket egy feloldott promise-ba csomagolja:

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

Kifejezetten a HTTP esetében a beépített `fetch()` már valódi JS promise-t ad vissza, és a saját PHP kliensén keresztül is használható [egyéni fetch-átvitellel](https://phasis.inline0.dev/hu/docs/interop/fetch-transport).

## Teljesítményre vonatkozó megjegyzések

Minden gazdafüggvény-hívás egyszer lépi át a PHP↔JS határt az argumentumokkal, egyszer pedig a visszatérési értékkel. Mindkét konverzió másolatot készít, ezért a költségük O(size of value): a primitívek konstans idejűek, a tömbök és objektumok költsége pedig méretükkel arányos.

Gyakran futó belső ciklusoknál részesítse előnyben azt az egy hívást, amely több munkát végez, és ne adjon át nagy tömböket oda-vissza, ha csak néhány mező változik.
