
# API

Vykdymo API sudaro viena klasė: `Phasis\Engine`. Visa kita (`JsValue`, `JsFunction`, `JsObject`, …) yra vidinė variklio dalis ir pasiekiama tik prieglobos funkcijoms, kurios sujungia PHP↔JS. Kita viešoji sąsaja — atskiras [ analizatorius API](https://phasis.inline0.dev/lt/docs/parser) (`Phasis\Parser\Parser` ir `Phasis\Ast` klasės), analizuojantis šaltinio kodą jo nevykdydamas.

## Sukūrimas

```php
use Phasis\Engine;

$engine = new Engine();                 // lazy mode (default)
$engine = new Engine(eager: true);      // install every built-in up front
```

Naujas `Engine` suteikia visą standartinę biblioteką — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` ir pagrindines kalbos integruotas funkcijas; jos įdiegiamos iš karto, kad specifikacija veiktų (klasės neveiks be `Function.prototype`, async/await — be `Promise`, regex literalai — be `RegExp`). Visa kita — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` ir masyvų buferių šeima, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, silpnųjų nuorodų šeima, `Temporal`, `ShadowRealm`, Web Platform Pack ir Fetch Pack — užregistruojama kaip pakaitinis prieigos metodas ir sukuriama pirmą kartą nuskaičius. Globaliojo objekto reikšmę galima skaityti įprastai: veikia `Map`, `typeof Map`, `'Map' in globalThis` ir `Object.keys(globalThis).includes('Map')`, o po pirmojo skaitymo pakaitinis metodas pakeičiamas įprastu duomenų deskriptoriumi.

Perduokite `eager: true`, jei reikia elgsenos iki atidėtojo įdiegimo — visos integruotos funkcijos įdiegiamos kuriant objektą, o introspekcija mato visą deskriptoriaus sąsają. Atitikties testų karkasas (`bin/test262`, `bin/wpt`) kuria variklius išankstinio įdiegimo režimu, kad test262/WPT matytų numatytų formų deskriptorius.

## eval

```php
public function eval(string $source): mixed
```

Analizuoja ir vykdo `$source` kaip klasikinį scenarijų variklio visuotinėje srityje, tada vykdo įvykių ciklą, kol jis ištuštėja. Grąžina paskutinės įvertintos išraiškos reikšmę, konvertuotą į PHP (arba `null`, jei programos pabaigoje nebuvo išraiškos). Taisyklės aprašytos skiltyje [Reikšmių konvertavimas](https://phasis.inline0.dev/lt/docs/interop/values).

```php
$result = $engine->eval('1 + 2');           // 3
$result = $engine->eval('JSON.stringify({a: 1})');  // '{"a":1}'
$result = $engine->eval('[1,2,3].map(x => x * 2)'); // [2, 4, 6] (PHP array)
```

Promise reikšmė neišskleidžiama: `eval('Promise.resolve(5)')` grąžina tuščią masyvą. Kadangi grįžus iš `eval()` įvykių ciklas jau būna ištuštėjęs, išsaugokite įvykdytą reikšmę globaliajame kintamajame ir perskaitykite ją antru `eval()` iškvietimu:

```php
$engine->eval('let out; (async () => { out = await Promise.resolve(9); })();');
$engine->eval('out');   // 9
```

Aiškiai išmesta JS `throw`, kurios niekas neperima, pateikiama kaip `Phasis\Exceptions\JsThrowable`; jos pranešimas yra `"Name: message"`, o savybėje `$jsValue` saugoma išmesta reikšmė `JsValue`. Variklio pačios sukeltos klaidos, pavyzdžiui, savybės skaitymas iš `null` arba neapibrėžtas kintamasis, pateikiamos kaip `Phasis\Exceptions\TypeError`, `ReferenceError` arba `RangeError`. Visos jos paveldi `Phasis\Exceptions\RuntimeError`; analizavimo klaidos paveldi `Phasis\Exceptions\SyntaxError`. Žr. [Išimtys](#exceptions).

```php
try {
    $engine->eval('throw new Error("boom")');
} catch (\Phasis\Exceptions\JsThrowable $e) {
    echo $e->getMessage();
    // Error: boom
}
```

## evalAsModule

```php
public function evalAsModule(string $source, ?string $path = null): mixed
```

Įvertina `$source` kaip ES modulį: pasiekiami `import` / `export`, aukščiausio lygio `await` ir `import.meta`, o santykiniai specifikatoriai sprendžiami pagal `$path` (absoliutų failo kelią). Įvykių ciklas vykdomas kaip naudojant `eval()`, o rezultatas visada yra `null`; rezultatus skaitykite per globaliuosius kintamuosius arba konsolės buferį.

```php
$path = __DIR__ . '/entry.mjs';
$engine->evalAsModule(file_get_contents($path), $path);
```

Įkėliklis išsprendžia tik santykinius ir absoliučius kelius; žr. [Žinomi apribojimai](https://phasis.inline0.dev/lt/docs/compatibility/limitations#module-loader).

## execFile

```php
public function execFile(string $path): mixed
```

Nuskaito failą iš disko ir perduoda jo turinį į `eval()`, todėl šis vykdomas kaip klasikinis scenarijus, nepriklausomai nuo failo plėtinio; failas su `import` / `export` sukelia SyntaxError. Tikrasis failo kelias tampa dinaminių `import()` specifikatorių baziniu keliu. Modulių failams naudokite `evalAsModule()`.

```php
$engine->execFile(__DIR__ . '/script.js');
```

Jei failo nepavyksta nuskaityti, PHP pateikia įspėjimą, o `execFile()` išmeta paprastą `\RuntimeException` („Cannot read file: …“).

## setGlobal

```php
public function setGlobal(string $name, mixed $value): void
```

Susieja globalųjį kintamąjį, matomą visuose vėlesniuose JS vertinimuose. PHP reikšmės konvertuojamos į JS:

| PHP tipas | JS tipas |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (net jei tai funkcijos pavadinimas, pvz., `'strlen'`) |
| masyvas (sąrašas) | `Array` (kopija) |
| masyvas (asociatyvusis) | `Object` (kopija) |
| `Closure`, iškviečiamas objektas, `[$object, 'method']` | `function` |
| bet kuris kitas objektas, generatorius arba išteklius | `undefined` |

Masyvai kopijuojami: JS atlikti pakeitimai juose neatsispindi PHP masyve. Paprastųjų PHP objektų dar negalima perduoti ([18 problema](https://github.com/inline0/phasis/issues/18)); apie galimus sprendimus žr. [Bendrinami objektai](https://phasis.inline0.dev/lt/docs/interop/shared-objects).

```php
$engine->setGlobal('config', ['debug' => true, 'version' => '1.0']);
$engine->setGlobal('log', fn(string $msg) => error_log($msg));
```

JS viduje:

```javascript
console.log(config.version);   // "1.0"
log('hello');                  // calls the PHP closure
```

## call

```php
public function call(string $name, mixed ...$args): mixed
```

Iškviečia globaliąją JS funkciją su PHP argumentais (konvertuojamais taip pat, kaip naudojant `setGlobal`). Grąžinama reikšmė konvertuojama atgal į PHP. Iškvietus neapibrėžtą vardą išmetama `Phasis\Exceptions\ReferenceError`; iškvietus globalųjį objektą, kuris nėra funkcija, išmetama `Phasis\Exceptions\TypeError`.

```php
$engine->eval('function add(a, b) { return a + b; }');
echo $engine->call('add', 2, 3);   // 5
```

## Konsolės išvestis

```php
public function getConsoleOutput(): string
public function getConsoleLines(): array
public function clearConsole(): void
```

`console.log`, `console.info`, `console.warn`, `console.error` ir `console.debug` nieko nespausdina. Kiekvienas iškvietimas prideda vieną suformatuotą eilutę į variklio buferį. `getConsoleLines()` grąžina buferio eilutes kaip eilučių sąrašą, `getConsoleOutput()` sujungia jas į vieną eilutę naudodama `\n`, o `clearConsole()` išvalo buferį. Buferis neišvalomas tarp `eval()` iškvietimų.

```php
$engine->eval('console.log("a", 1); console.warn({ b: 2 })');
$engine->getConsoleLines();    // ['a 1', '{ b: 2 }']
echo $engine->getConsoleOutput();
// a 1
// { b: 2 }
$engine->clearConsole();
```

## setLimit

```php
public function setLimit(string $limit, int $value): void
```

Nustato vykdymo metu keičiamą išteklių ribą. Yra tik viena:

| Riba | Numatytoji reikšmė | Aprašymas |
|---|---|---|
| `maxLoopIterations` | `100000` | didžiausias iteracijų skaičius per `for` / `while` / `do…while` medžio interpretatoriaus vykdomą ciklą |

```php
$engine->setLimit('maxLoopIterations', 1_000_000);
```

Kiti pavadinimai tyliai ignoruojami; nėra eilučių ilgio, konsolės išvesties ar realaus laiko apribojimų.

Ciklo riba dar ne visur taikoma. Baitkodo VM vykdomi ciklai, apimantys funkcijų turinius ir aukščiausio lygio programas, kurias priima baitkodo kompiliatorius, neskaičiuojami, todėl `while (true) {}` veikia, kol jo nesustabdo PHP ([19 problema](https://github.com/inline0/phasis/issues/19)). Nepasitikėkite šia riba vykdydami nepatikimą kodą; naudokite PHP `max_execution_time` arba atskirą procesą.

Pasiekus ribą, klaidą galima perimti naudojant JS `try` / `catch` (kaip `Error` su pranešimu „Maximum loop iterations exceeded“). Jei niekas jos neperima, ji pasiekia PHP kaip `Phasis\Exceptions\InternalError`, kuri paveldi `Phasis\Exceptions\RuntimeError`, o ne `JsThrowable`.

Dar dvi variklyje užkoduotos ir vykdymo metu nekeičiamų ribų: `Phasis\Runtime\CallStack::maxDepth` (4,096 JS kadrai) ir JsToPhp transpiliuojamų uždarųjų funkcijų gylio apsauga (8,192 PHP kadrai). Viršijus bet kurią, išmetama „Maximum call stack size exceeded“; ji taip pat pasiekia PHP kaip `InternalError`. Šios ribos gerokai viršija bet kurios teisėtos JS programos poreikius — jas padidinti galima keičiant kodą, o ne konfigūracijos parinktį.

## reset

```php
public function reset(): void
```

Išvalo visus naudotojo apibrėžtus globaliuosius kintamuosius ir konsolės buferį, iš naujo sukuria standartinę biblioteką bei įvykių ciklą.

Variklyje, sukurtame numatytuoju atidėtojo įdiegimo režimu, `reset()` šiuo metu pašalina visas atidėtai įdiegtas integruotas funkcijas: vėliau `Map`, `JSON`, `Math`, `console`, `fetch` ir kitos tampa neapibrėžtos ([22 problema](https://github.com/inline0/phasis/issues/22)). Kol tai bus ištaisyta, vietoj to sukurkite naują `Engine` arba kurdami perduokite `eager: true` — tada veikia `reset()`.

## Įvykių ciklas

Variklyje yra JavaScript įvykių ciklas; globaliajame objekte pasiekiami `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` ir `queueMicrotask`. Mikroužduotys (Promise tvarkyklės, `queueMicrotask`) ištuštinamos tarp kiekvienos makroužduoties; laikmačiai suveikia nustatyta termino tvarka per kiekvienos srities užduočių eilę.

```php
public function runEventLoop(): void
public function tickEventLoop(): bool
public function pendingTaskCount(): int
public function getEventLoop(): \Phasis\Runtime\EventLoop
```

`eval()` ir `evalAsModule()` automatiškai iškviečia `runEventLoop()` pasibaigus sinchroniniam kodui. Todėl scenarijus, kuris aukščiausiame lygyje nustato `setTimeout(cb, 5000)`, įvykdo `cb` prieš grįžtant `eval()` — ciklas naudoja **virtualųjį laiką** ir perstumia laikrodį iki kito laukiančio termino, užuot laukęs PHP. Atitinka `node script.js` semantiką.

Jei norite įterpti į realiuoju laiku veikiančią sistemą (ilgalaikius serverius, UI integraciją ar planavimą pagal tikrą laikrodį), `tickEventLoop()` įvykdo vieną iteraciją (ištuština mikroužduotis, paleidžia atėjusius laikmačius) ir grįžta. `pendingTaskCount()` pateikia laukiančių laikmačių sąrašą; įterpianti programa gali valdyti savo ciklą:

```php
while ($engine->pendingTaskCount() > 0) {
    $engine->tickEventLoop();
    usleep(1000);
}
```

Nesibaigiantis `setInterval` yra apribotas: `runEventLoop()` riboja iki 100,000 ciklų ir išmeta paprastą `\RuntimeException` — JS kodo klaida pateikiama kaip aiški PHP klaida, o ne pakibimas. Intervalas lieka suplanuotas, todėl kiekvienas vėlesnis `eval()` tame pačiame variklyje išmeta tą pačią klaidą; po to variklį pašalinkite. Neperimtos laikmačio atgalinio iškvietimo klaidos perduodamos per `error_log()`, o ciklas vykdomas toliau; viena sugedusi laikmačio užduotis nesustabdo programos.

## Fetch kabliai

Phasis pagal numatytuosius nustatymus pateikiamas su įjungtu `fetch()`; pagrindinis transportas yra PHP `ext-curl`. Trys kabliai leidžia įterpiančiai programai pakeisti transportą, apriboti užklausas arba įjungti slapukų saugyklą. Visi priima iškviečiamą PHP arba objekto metodą; reikšmių formos aprašytos skiltyje [Žiniatinklio API](https://phasis.inline0.dev/lt/docs/compatibility/web-apis).

### setFetchTransport

```php
public function setFetchTransport(callable $transport): void
```

Pakeičia numatytąjį CurlTransport. Iškviečiamai funkcijai perduodamas užklausos deskriptorius ir pasirinktinis `AbortSignal` JsObject; ji grąžina atsakymo deskriptorių.

```php
// Use Guzzle as the transport instead of curl
$client = new GuzzleHttp\Client();
$engine->setFetchTransport(function (array $req, $signal) use ($client) {
    $r = $client->request($req['method'], $req['url'], [
        'headers' => $req['headers'],
        'body' => $req['body'],
        'http_errors' => false,
    ]);
    return [
        'status' => $r->getStatusCode(),
        'statusText' => $r->getReasonPhrase(),
        'headers' => array_map(
            fn ($name, $values) => array_map(fn ($v) => [$name, $v], $values),
            array_keys($r->getHeaders()),
            $r->getHeaders(),
        ),
        'body' => (string) $r->getBody(),
    ];
});
```

Dažnas integravimo būdas: testuose įdiegti imitacinį transportą, o produkcinėje aplinkoje — tikrą curl transportą.

### setFetchPolicy

```php
public function setFetchPolicy(callable $hook): void
```

Išankstinio patikrinimo politikos kablys. Gauna `Request` JsObject; užklausai atmesti gali išmesti klaidą, perrašyti ją grąžindamas pakeistą `Request` arba leisti grąžindamas `null` / negrąžindamas reikšmės.

```php
// Block non-HTTPS and inject auth on a specific host
$engine->setFetchPolicy(function ($req) {
    $url = $req->get('url')->value;
    if (str_starts_with($url, 'http://')) {
        throw new \RuntimeException('HTTPS only');
    }
    if (str_contains($url, 'api.example.com')) {
        $req->get('headers')->call('set', 'Authorization', 'Bearer ...');
    }
    return null;
});
```

### setCookieJar

```php
public function setCookieJar(mixed $jar): void
```

Pasirinktinai įjungiama slapukų saugykla. Priima bet kurį objektą, turintį `get(string $url): string` ir `set(string $url, string $header): void` metodus — PHP arba JS pusėje.

```php
class SimpleJar
{
    private array $cookies = [];
    public function get(string $url): string { return implode('; ', $this->cookies); }
    public function set(string $url, string $header): void { $this->cookies[] = $header; }
}
$engine->setCookieJar(new SimpleJar());
```

Be `setCookieJar()`, `fetch` ignoruoja ir gaunamas `Set-Cookie`, ir siunčiamas `Cookie` antraštes — pagal numatytuosius nustatymus **slapukai nenaudojami**.

## Išimtys

| Klasė | Paveldi iš | Kada naudojama |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | aiškiai išmesta JS `throw`, kurios niekas neperėmė |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | variklio sukelta ir niekieno neperimta klaida (pvz., `null.x` arba neapibrėžtas kintamasis) |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | viršyta ciklo riba arba iškvietimų steko riba |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | visų pirmiau išvardytų klasių bazinė klasė |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | šaltinio kodo nepavyksta išanalizuoti |
| `\RuntimeException` | | `execFile()` negali nuskaityti failo; įvykių ciklas pasiekė 100,000 ciklų ribą |

Jei norite perimti visas scenarijaus klaidas, perimkite `Phasis\Exceptions\RuntimeError` ir `Phasis\Exceptions\SyntaxError` arba tiesiog `\RuntimeException`.

Jūsų pačių PHP prieglobos funkcijų išmetamos išimtys tvarkomos kitaip: šiuo metu jos nepakitusios praeina pro JS `try` / `catch` ir pasiekia `eval()` iškvietusį objektą, išsaugodamos pradinę klasę ([20 problema](https://github.com/inline0/phasis/issues/20)). Žr. [Prieglobos funkcijos](https://phasis.inline0.dev/lt/docs/interop/host-functions#throwing-back-into-js).

## Gyvavimo ciklas

`Engine` sukuriamas greitai (šaltai per kelias milisekundes), o standartinė biblioteka įdiegiama atidėtai. Daugeliu integravimo atvejų užtenka vieno ilgai veikiančio variklio kiekviename procese. Naudodami keliems nuomininkams, kiekvienam nuomininkui arba užklausai skirkite atskirą variklį, kad globalieji kintamieji nebūtų bendrinami. Šiuo metu patikimiausias būdas pradėti nuo švarios būsenos — sukurti naują variklį (žr. [atstatymą](#reset)).

```php
foreach ($requests as $req) {
    $engine = new Engine();
    $engine->setGlobal('request', ['path' => $req->path, 'query' => $req->query]);
    $engine->execFile($req->scriptPath);
}
```
