
# Funcții gazdă

Orice funcție de tip closure sau funcție apelabilă PHP transmisă motorului prin `setGlobal()` devine o funcție apelabilă JS. Nu este necesară înregistrarea manuală, nu există schemă sau marshalling — transmiteți pur și simplu funcția apelabilă.

## Noțiuni de bază

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

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

Când `greet("world")` rulează în JS:

1. Șirul `"world"` este convertit din JS în PHP.
2. Funcția closure PHP este apelată cu argumentul convertit.
3. Valoarea returnată este convertită din PHP înapoi în JS.

Conversia urmează regulile de [conversie a valorilor](https://phasis.inline0.dev/ro/docs/interop/values). Argumentele sunt copii: un tablou sau obiect JS ajunge ca tablou PHP, iar modificarea lui în PHP nu schimbă valoarea JS.

## Numărul și parametrii argumentelor

Motorul transmite argumentele JS funcției apelabile, în ordine și așa cum sunt; nu îi inspectează semnătura. Funcțiile closure obișnuite ignoră argumentele suplimentare, iar parametrii variadici le colectează. Argumentele lipsă nu sunt completate: dacă JS transmite mai puține argumente decât cere funcția apelabilă, PHP aruncă `ArgumentCountError`. Atribuiți valori implicite parametrilor opționali.

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

## Conversia tipurilor

Argumentele sunt convertite în PHP conform regulilor JS→PHP. Motorul apelează funcția folosind cod care declară `strict_types=1`, astfel că PHP verifică strict tipurile parametrilor scalari și nu face conversii implicite: un număr JS transmis unui parametru `string` este `TypeError`. Ca orice excepție PHP dintr-o funcție gazdă, aceasta ajunge la apelantul lui `eval()`, nu la JS (vedeți mai jos).

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

Fie convertiți manual în interiorul funcției closure (`(string) $value`), fie folosiți tipuri union (`int|string`).

## Aruncarea excepțiilor înapoi în JS

Excepțiile obișnuite PHP aruncate în interiorul unei funcții gazdă **nu** sunt convertite în erori JS în prezent. Ele trec prin toate gestionările JS `try` / `catch` și `.catch()`, apoi ajung la codul PHP care a apelat `eval()` sau `call()`, păstrându-și clasa inițială ([problema nr. 20](https://github.com/inline0/phasis/issues/20)). Sunt incluse `\Exception`, `\RuntimeException`, `\DivisionByZeroError` și erorile PHP `\TypeError` și `\ArgumentCountError` pentru argumente invalide.

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

Pentru a arunca o eroare pe care codul JS o poate captura, aruncați una dintre clasele de erori proprii motorului: `Phasis\Exceptions\TypeError`, `RangeError` sau `ReferenceError` ajung în JS ca tipul de eroare JS corespunzător, iar orice altă `Phasis\Exceptions\RuntimeError` ajunge ca un `Error` simplu, fiecare cu mesajul transmis de dumneavoastră.

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

## Returnarea valorilor complexe

Valoarea returnată este convertită conform regulilor PHP→JS. Pentru a returna un obiect literal JS, returnați un tablou asociativ 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);
');
```

Pentru a returna un tablou JS, returnați o listă secvențială 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
```

Returnarea unui obiect PHP, inclusiv a unei promisiuni PHP de la ReactPHP, Amp sau Guzzle, produce JS `undefined` (vedeți [Conversia valorilor](https://phasis.inline0.dev/ro/docs/interop/values)).

## Funcții gazdă asincrone

Funcțiile gazdă sunt sincrone. O promisiune PHP sau un alt obiect thenable nu este așteptat: se convertește în `undefined`, astfel că `await hostFn()` se rezolvă cu `undefined`. Efectuați operația sincron și returnați rezultatul; codul JS poate totuși să apeleze cu `await`, ceea ce încadrează valoarea într-o promisiune rezolvată:

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

În cazul HTTP, funcția încorporată `fetch()` returnează deja o promisiune reală JS și poate fi direcționată prin propriul client PHP cu un [transport fetch personalizat](https://phasis.inline0.dev/ro/docs/interop/fetch-transport).

## Note despre performanță

Fiecare apel al unei funcții gazdă traversează o dată limita PHP↔JS pentru argumente și o dată pentru valoarea returnată. Ambele conversii fac copii, deci costurile sunt O(size of value) — operațiile cu primitive au timp constant, iar cele cu tablouri și obiecte sunt proporționale cu dimensiunea lor.

În buclele interne foarte solicitate, preferați un singur apel care efectuează mai multă muncă în locul multor apeluri mici și evitați să transmiteți tablouri mari înainte și înapoi când se modifică doar câteva câmpuri.
