
# API

KäivitusAPI koosneb ühest klassist: `Phasis\Engine`. Kõik muu (`JsValue`, `JsFunction`, `JsObject`, …) on mootori sisemine ning avatud ainult hostifunktsioonidele, mis ühendavad PHP↔JS. Ainus muu avalik liides on eraldiseisev [parser API](https://phasis.inline0.dev/et/docs/parser) (klassid `Phasis\Parser\Parser` ja `Phasis\Ast`), mis parsib lähtekoodi seda käivitamata.

## Loomine

```php
use Phasis\Engine;

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

Uus `Engine` pakub kogu standardteegi — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` ja keele põhisisseehitatud funktsioonid lisatakse kohe, et spetsifikatsioon toimiks (klasse ei saa olla ilma `Function.prototype`-ta, async/await-i ilma `Promise`-ta ega regulaaravaldiste literaale ilma `RegExp`-ta). Kõik muu — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` ja massiivipuhvrite perekond, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, nõrkade viidete perekond, `Temporal`, `ShadowRealm`, Web Platform Pack ja Fetch Pack — registreeritakse kohatäitja juurdepääsufunktsioonina ning luuakse esimesel lugemisel. Globaalse väärtuse lugemine on läbipaistev: `Map`, `typeof Map`, `'Map' in globalThis` ja `Object.keys(globalThis).includes('Map')` toimivad kõik ning pärast esimest lugemist asendatakse kohatäitja tavalise andmekirjeldajaga.

Anna `eager: true`, kui vajad konkreetselt laiska laadimiseta käitumist — kõik sisseehitatud funktsioonid installitakse loomisel ning kogu kirjeldaja liides on introspektsiooniks nähtav. Vastavustestide käitaja (`bin/test262`, `bin/wpt`) loob mootorid innukas režiimis, et test262/WPT näeks oodatud kirjeldajate struktuure.

## eval

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

Parsib ja käivitab `$source` klassikalise skriptina mootori globaalses ulatuses ning käitab seejärel sündmussilmust, kuni see tühjeneb. Tagastab viimase hinnatud avaldise väärtuse, teisendatuna PHP-ks (või `null`-ks, kui programmi lõpus avaldist pole). Reegleid vaata jaotisest [Väärtuste teisendamine](https://phasis.inline0.dev/et/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'i ei avata: `eval('Promise.resolve(5)')` tagastab tühja massiivi. Kuna sündmussilmus on tühjaks töötanud selleks ajaks, kui `eval()` tagastab, salvesta lahendatud väärtus globaalsesse muutujasse ja loe see teise `eval()` abil:

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

Kui selgesõnalist JS `throw`-i ei püüta kinni, ilmub see `Phasis\Exceptions\JsThrowable`-na, mille sõnum on `"Name: message"` ja mille omadus `$jsValue` sisaldab visatud väärtust `JsValue`. Mootori enda tekitatud vead, näiteks omaduse lugemine väärtuselt `null` või määratlemata muutuja kasutamine, ilmuvad selle asemel kui `Phasis\Exceptions\TypeError`, `ReferenceError` või `RangeError`. Kõik need laiendavad `Phasis\Exceptions\RuntimeError`-i; parsinguvead laiendavad `Phasis\Exceptions\SyntaxError`-i. Vt [Erandid](#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
```

Hindab `$source`-i ES-moodulina: saadaval on `import` / `export`, tipptaseme `await` ja `import.meta` ning suhtelised määrajad lahendatakse asukoha `$path` järgi (absoluutne failitee). Käitab sündmussilmust nagu `eval()` ja tagastab alati `null`; loe tulemusi globaalsete muutujate või konsoolipuhvri kaudu.

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

Laadija lahendab ainult suhtelisi ja absoluutseid failiteid; vt [Teadaolevad piirangud](https://phasis.inline0.dev/et/docs/compatibility/limitations#module-loader).

## execFile

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

Loeb faili kettalt ja edastab selle sisu funktsioonile `eval()`, nii et see käivitatakse klassikalise skriptina laiendist olenemata; fail `import` / `export`-ga põhjustab SyntaxErrori. Faili tegelikust teest saab dünaamiliste `import()` määrajate alusteekond. Moodulifailide jaoks kasuta `evalAsModule()`.

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

Kui faili ei saa lugeda, väljastab PHP hoiatuse ja `execFile()` viskab lihtsa `\RuntimeException`-i ("Cannot read file: …").

## setGlobal

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

Seob globaalse muutuja, mis on nähtav kõigi järgnevate JS-i hindamiste jaoks. PHP väärtused teisendatakse tüübiks JS:

| PHP tüüp | JS tüüp |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (isegi kui see tähistab funktsiooni, näiteks `'strlen'`) |
| massiiv (loend) | `Array` (koopia) |
| massiiv (assotsiatiivne) | `Object` (koopia) |
| `Closure`, käivitatav objekt, `[$object, 'method']` | `function` |
| mis tahes muu objekt, generaator või ressurss | `undefined` |

Massiivid kopeeritakse: muudatused, mida JS neis teeb, ei kajastu PHP massiivis. Lihtsaid PHP objekte ei saa veel edastada ([issue #18](https://github.com/inline0/phasis/issues/18)); lahendusi vaata jaotisest [Jagatud objektid](https://phasis.inline0.dev/et/docs/interop/shared-objects).

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

Funktsiooni JS sees:

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

## call

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

Käivitab globaalset JS funktsiooni argumentidega PHP (teisendatakse nagu `setGlobal` puhul). Tagastusväärtus teisendatakse tagasi tüübiks PHP. Määratlemata nime käivitamine viskab `Phasis\Exceptions\ReferenceError`-i; globaalse väärtuse käivitamine, kui see pole funktsioon, viskab `Phasis\Exceptions\TypeError`-i.

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

## Konsooliväljund

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

`console.log`, `console.info`, `console.warn`, `console.error` ja `console.debug` ei väljasta teksti. Iga väljakutse lisab mootori puhvrisse ühe vormindatud rea. `getConsoleLines()` tagastab puhverdatud read stringide loendina, `getConsoleOutput()` tagastab need `\n`-ga ühendatuna ja `clearConsole()` tühjendab puhvri. Puhvrit ei tühjendata `eval()` väljakutsete vahel.

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

Määrab käitusajal muudetava ressursipiirangu. Neid on täpselt üks:

| Piirang | Vaikimisi | Kirjeldus |
|---|---|---|
| `maxLoopIterations` | `100000` | maksimaalne iteratsioonide arv puupõhise interpretaatori käivitatava `for` / `while` / `do…while` tsüklikäivituse kohta |

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

Kõiki muid nimesid ignoreeritakse vaikselt; stringi pikkusele, konsooliväljundile ega seinakellaajale piiranguid pole.

Tsüklipiirangut ei rakendata veel kõikjal. Baidikoodi VM-is käitatavaid tsükleid, mis hõlmab funktsioonide kehasid ja tipptaseme programme, mida baidikoodi kompilaator aktsepteerib, ei loendata kunagi. Seega töötab `while (true) {}` kuni PHP selle peatab ([issue #19](https://github.com/inline0/phasis/issues/19)). Ära looda, et see peatab ebausaldusväärse koodi; kasuta PHP-i `max_execution_time`-i või eraldi protsessi.

Piirangu saavutamisel saab vea kinni püüda JS `try` / `catch` abil (see on `Error`, mille sõnum on "Maximum loop iterations exceeded"). Kui seda kinni ei püüta, jõuab see PHP-ni kui `Phasis\Exceptions\InternalError`, mis laiendab `Phasis\Exceptions\RuntimeError`-i, mitte `JsThrowable`-i.

Mootorisse on sisse ehitatud veel kaks ülempiiri, mida ei saa käitusajal muuta: `Phasis\Runtime\CallStack::maxDepth` (4,096 JS kaadrit) ja JsToPhp transleeritud sulundi sügavuse piirang (8,192 PHP kaadrit). Mõlema ületamisel visatakse "Maximum call stack size exceeded", mis jõuab samuti PHP-ni kui `InternalError`. Need piirid ületavad tublisti mis tahes õiguspärase JS programmi vajadused — nende suurendamine nõuab koodimuudatust, mitte seadistust.

## reset

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

Tühjendab kõik kasutaja määratud globaalsed muutujad ja konsoolipuhvri ning loob standardteegi ja sündmussilmuse uuesti.

Vaikimisi laisa laadimisega režiimis loodud mootoris kaotab `reset()` praegu kõik laisalt installitud sisseehitatud funktsioonid: seejärel on `Map`, `JSON`, `Math`, `console`, `fetch` ja ülejäänud määratlemata ([issue #22](https://github.com/inline0/phasis/issues/22)). Kuni see on parandatud, loo selle asemel uus `Engine` või loo mootor parameetriga `eager: true`, mille korral `reset()` töötab.

## Sündmussilmus

Mootor sisaldab JavaScript sündmussilmust, millel on globaalsel objektil `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` ja `queueMicrotask`. Mikroülesanded (Promise'i käsitlejad, `queueMicrotask`) viiakse lõpuni iga makroülesande vahel; taimerid käivituvad tähtaja järjekorras, iga käituskonteksti ülesannete järjekorra kaudu.

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

`eval()` ja `evalAsModule()` kutsuvad pärast sünkroonse koodi lõppu automaatselt välja `runEventLoop()`. Seega käivitab skript, mis ajastab tipptasemel `setTimeout(cb, 5000)`, selle `cb` enne, kui `eval()` tagastab — silmus kasutab **virtuaalaega** ja nihutab kella järgmise ootel tähtajani, selle asemel et PHP magada. Vastab `node script.js` semantikale.

Reaalajas manustamiseks (pikaajalised serverid, UI integratsioon, päris kellal põhinev ajastamine) käivitab `tickEventLoop()` ühe iteratsiooni (viib mikroülesanded lõpuni ja käivitab kõik tähtaja saavutanud taimerid) ning tagastab. `pendingTaskCount()` annab teada ootel taimeritest; manustaja saab käitada oma tsüklit:

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

Lõputu `setInterval` on piiratud: `runEventLoop()` seab ülempiiriks 100,000 tsüklit ja viskab lihtsa `\RuntimeException`-i — vea põhjus on selgelt JS koodis, mis põhjustab arusaadava PHP vea, mitte hangumise. Intervall jääb ajastatuks, mistõttu iga hilisem `eval()` samas mootoris viskab sama vea; seejärel kõrvalda mootor kasutusest. Taimeri tagasihelistuses kinni püüdmata vead väljastatakse funktsiooniga `error_log()` ja silmus jätkab tööd; üks vigane taimer ei peata programmi.

## Fetchi haagid

Phasis tarnitakse vaikimisi lubatud `fetch()`-ga, mille aluseks on PHP `ext-curl`. Kolm haaki võimaldavad manustajal vahetada transpordikihti, piirata päringuid või lubada küpsiste salvestamise. Kõik kolm aktsepteerivad kas PHP väljakutsutavat väärtust või objekti meetodit; väärtuse kuju lepingut vaata jaotisest [Veebi API-d](https://phasis.inline0.dev/et/docs/compatibility/web-apis).

### setFetchTransport

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

Asendab vaikimisi kasutatava CurlTransporti. Väljakutsutav väärtus saab päringu kirjeldaja ja valikulise `AbortSignal` JsObjecti ning tagastab vastuse kirjeldaja.

```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(),
    ];
});
```

Levinud manustamisviis: testides paigaldatakse näidistransport, tootmiskeskkonnas päris curl-transport.

### setFetchPolicy

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

Päringueelne poliitikahaak. Saab `Request` JsObjecti; võib keelamiseks vea visata, ümberkirjutamiseks muudetud `Request` tagastada või lubamiseks tagastada `null`/mitte midagi.

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

Valikuline küpsiste salvesti. Aktsepteerib mis tahes objekti, millel on `get(string $url): string` ja `set(string $url, string $header): void` — kas PHP- või JS-poolel.

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

Ilma `setCookieJar()`-ta ignoreerib `fetch` nii sissetulevaid `Set-Cookie`- kui ka väljaminevaid `Cookie`-päiseid — vaikimisi küpsiseid **ei kasutata**.

## Erandid

| Klass | Laiendab | Millal |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | selgesõnaline JS `throw`, mida kinni ei püütud |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | mootori enda tekitatud viga (näiteks `null.x` või määratlemata muutuja), mida kinni ei püütud |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | ületati tsüklite piirang või väljakutsete pinu ülempiir |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | ülaltoodute põhiklass |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | lähtekoodi ei saa parsida |
| `\RuntimeException` | | `execFile()` ei saa faili lugeda; sündmussilmus jõudis 100,000 tsükli piirini |

Skripti kõigi tõrgete kinnipüüdmiseks püüa kinni `Phasis\Exceptions\RuntimeError` ja `Phasis\Exceptions\SyntaxError` või lihtsalt `\RuntimeException`.

Teie enda PHP hostifunktsioonide visatud erandid on teistsugused: praegu läbivad need JS `try` / `catch` muutmata kujul ning jõuavad funktsiooni `eval()` väljakutsujani oma algse klassiga ([issue #20](https://github.com/inline0/phasis/issues/20)). Vt [Hostifunktsioonid](https://phasis.inline0.dev/et/docs/interop/host-functions#throwing-back-into-js).

## Elutsükkel

`Engine` loomine on kiire (~paar ms külmkäivituse korral) ja standardteek laaditakse laisalt. Enamiku manustamisjuhtude puhul piisab ühest pikaajalisest mootorist protsessi kohta. Mitme rentnikuga kasutamisel anna igale rentnikule või päringule oma mootor, et globaalsed muutujad nende vahel ei lekiks. Praegu on usaldusväärseim viis puhta algseisu saamiseks luua uus mootor (vt [reset](#reset)).

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