
# Resursdatora funkcijas

Jebkura PHP slēgtā funkcija vai izsaucams objekts, kas nodots dzinējam, izmantojot `setGlobal()`, kļūst par izsaucamu JS funkciju. Manuāla reģistrācija nav vajadzīga, nav shēmas vai maršalizācijas — vienkārši nododiet izsaucamo objektu.

## Pamati

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

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

Kad `greet("world")` darbojas JS:

1. Virkne `"world"` tiek pārveidota no JS uz PHP.
2. Tiek izsaukta PHP slēgtā funkcija ar pārveidoto argumentu.
3. Atgrieztā vērtība tiek pārveidota no PHP atpakaļ uz JS.

Pārveide notiek saskaņā ar [vērtību pārveides noteikumiem](https://phasis.inline0.dev/lv/docs/interop/values). Argumenti ir kopijas: JS masīvs vai objekts tiek saņemts kā PHP masīvs, un tā izmainīšana PHP nemaina JS vērtību.

## Argumentu skaits un parametri

Dzinējs nodod JS argumentus izsaucamajam objektam norādītajā secībā un tieši tādus, kādi tie ir; tas nepārbauda signatūru. Parastās slēgtās funkcijas ignorē liekos argumentus, bet mainīga argumentu skaita parametri tos apkopo. Trūkstošie argumenti netiek aizpildīti: ja JS nodod mazāk argumentu, nekā nepieciešams izsaucamajam objektam, PHP izmet `ArgumentCountError`. Izvēles parametriem norādiet noklusējuma vērtības.

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

## Tipu automātiska pārveidošana

Argumenti tiek pārveidoti par PHP, izmantojot JS→PHP noteikumus. Dzinējs izsauc jūsu izsaucamo objektu no koda, kas deklarē `strict_types=1`, tāpēc PHP stingri pārbauda skalāro parametru tipus un neveic automātisku pārveidi: JS skaitlis, kas nodots `string` parametram, ir `TypeError`. Tāpat kā jebkurš PHP izņēmums no resursdatora funkcijas, tas nonāk pie `eval()` izsaucēja, nevis pie JS (skatiet tālāk).

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

Veiciet manuālu pārveidi slēgtajā funkcijā (`(string) $value`) vai izmantojiet apvienotos tipus (`int|string`).

## Kļūdu izmešana atpakaļ uz JS

Parasti PHP izņēmumi, kas izmesti resursdatora funkcijā, pašlaik **netiek** pārveidoti par JS kļūdām. Tie iziet cauri visiem JS `try` / `catch` un `.catch()` apstrādātājiem un nonāk līdz PHP kodam, kas izsauca `eval()` vai `call()`, saglabājot sākotnējo klasi ([20. problēma](https://github.com/inline0/phasis/issues/20)). Tas attiecas arī uz `\Exception`, `\RuntimeException`, `\DivisionByZeroError` un `\TypeError` un `\ArgumentCountError` PHP, kas tiek izmests nederīgu argumentu dēļ.

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

Lai izmestu kļūdu, ko var uztvert JS kods, izmetiet kādas no dzinēja kļūdu klasēm eksemplāru: `Phasis\Exceptions\TypeError`, `RangeError` vai `ReferenceError` tiek saņemti JS kā atbilstošā JS kļūdas tipa eksemplāri, bet jebkura cita `Phasis\Exceptions\RuntimeError` tiek saņemta kā parasts `Error`; katrā gadījumā tiek saglabāts jūsu ziņojums.

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

## Bagātīgu vērtību atgriešana

Atgrieztā vērtība tiek pārveidota saskaņā ar PHP→JS noteikumiem. Lai atgrieztu JS objekta literāli, atgrieziet PHP asociatīvo masīvu:

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

Lai atgrieztu JS masīvu, atgrieziet secīgu PHP sarakstu:

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

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

Atgriežot PHP objektu, tostarp PHP solījumu no ReactPHP, Amp vai Guzzle, tiek iegūts JS `undefined` (skatiet [Vērtību pārveide](https://phasis.inline0.dev/lv/docs/interop/values)).

## Asinhronās resursdatora funkcijas

Resursdatora funkcijas ir sinhronas. PHP solījums vai cits thenable objekts netiek gaidīts: tas tiek pārveidots par `undefined`, tāpēc `await hostFn()` tiek atrisināts par `undefined`. Veiciet darbu sinhroni un atgrieziet rezultātu; JS kods joprojām var izsaukt šo darbību ar `await`, kas iesaiņo vērtību atrisinātā solījumā:

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

Tieši HTTP gadījumā iebūvētais `fetch()` jau atgriež īstu JS solījumu, un to var novirzīt caur jūsu PHP klientu ar [pielāgotu izgūšanas transportu](https://phasis.inline0.dev/lv/docs/interop/fetch-transport).

## Veiktspējas piezīmes

Katrs resursdatora funkcijas izsaukums šķērso PHP↔JS robežu vienreiz argumentiem un vienreiz atgrieztajai vērtībai. Abas pārveides veido kopijas, tāpēc to sarežģītība ir O(size of value) — primitīviem tipiem laiks ir konstants, bet masīviem un objektiem tas pieaug proporcionāli to izmēram.

Veiktspējai svarīgās iekšējās cilpās dodiet priekšroku vienam izsaukumam, kas paveic vairāk, nevis daudziem maziem izsaukumiem, un nepārsūtiet lielus masīvus turp un atpakaļ, ja mainās tikai daži lauki.
