
# API

Vykonávacie API tvorí jedna trieda: `Phasis\Engine`. Všetko ostatné (`JsValue`, `JsFunction`, `JsObject`, …) je interné pre engine a sprístupňuje sa len hostiteľským funkciám, ktoré prepájajú PHP↔JS. Jediným ďalším verejným rozhraním je samostatný [parser API](https://phasis.inline0.dev/sk/docs/parser) (`Phasis\Parser\Parser` a triedy `Phasis\Ast`), ktorý parsuje zdrojový kód bez jeho vykonania.

## Vytvorenie

```php
use Phasis\Engine;

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

Čerstvá inštancia `Engine` sprístupňuje celú štandardnú knižnicu — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` a základné jazykové intrinzické funkcie sa inštalujú okamžite, aby fungovala špecifikácia (bez `Function.prototype` nemôžete mať triedy, bez `Promise` async/await a bez `RegExp` regulárne výrazy v literáloch). Všetko ostatné — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` a rodina array-buffer, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, rodina weak-ref, `Temporal`, `ShadowRealm`, Web Platform Pack a Fetch Pack — sa registruje ako zástupný prístupový prvok a vytvorí sa až pri prvom čítaní. Čítanie globálnej premennej je transparentné: `Map`, `typeof Map`, `'Map' in globalThis`, `Object.keys(globalThis).includes('Map')` fungujú a po prvom čítaní sa zástupný prvok nahradí bežným opisovačom dátovej vlastnosti.

Ak potrebujete správanie spred zavedenia lenivého načítavania, zadajte `eager: true` — všetky vstavané funkcie sa nainštalujú pri vytvorení a ich úplné deskriptory sú dostupné na introspekciu. Testovací rámec zhody (`bin/test262`, `bin/wpt`) vytvára enginy v režime eager, aby test262/WPT videli očakávané tvary deskriptorov.

## eval

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

Parsuje a vykoná `$source` ako klasický skript v globálnom rozsahu enginu a potom spúšťa event loop, kým sa nevyprázdni. Vráti hodnotu posledného vyhodnoteného výrazu prevedenú na PHP (alebo `null`, ak program nemal záverečný výraz). Pravidlá nájdete v časti [Prevod hodnôt](https://phasis.inline0.dev/sk/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 sa nerozbaľuje: `eval('Promise.resolve(5)')` vráti prázdne pole. Keďže event loop je vyčerpaný v čase, keď sa `eval()` vráti, uložte vyriešenú hodnotu do globálnej premennej a načítajte ju druhým volaním `eval()`:

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

Ak explicitné JS `throw` nič nezachytí, prejaví sa ako `Phasis\Exceptions\JsThrowable`, ktorého správa je `"Name: message"` a vlastnosť `$jsValue` obsahuje vyhodenú hodnotu `JsValue`. Chyby, ktoré vyvolá samotný engine, napríklad čítanie vlastnosti hodnoty `null` alebo nedefinovanej premennej, sa namiesto toho prejavia ako `Phasis\Exceptions\TypeError`, `ReferenceError` alebo `RangeError`. Všetky tieto triedy dedia od `Phasis\Exceptions\RuntimeError`; chyby parsovania dedia od `Phasis\Exceptions\SyntaxError`. Pozrite si [Výnimky](#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
```

Vyhodnotí `$source` ako modul ES: k dispozícii sú `import` / `export` a najvyššia úroveň `await` a `import.meta`; relatívne špecifikátory sa rozlišujú voči `$path` (absolútnej ceste k súboru). Event loop sa spúšťa ako pri `eval()` a vždy sa vráti `null`; výsledky čítajte z globálnych premenných alebo vyrovnávacej pamäte konzoly.

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

Načítavač rozlišuje iba relatívne a absolútne cesty; pozrite si [Známe obmedzenia](https://phasis.inline0.dev/sk/docs/compatibility/limitations#module-loader).

## execFile

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

Načíta súbor z disku a odovzdá jeho obsah do `eval()`, takže sa spustí ako klasický skript bez ohľadu na príponu; súbor s `import` / `export` zlyhá s chybou SyntaxError. Skutočná cesta k súboru sa stane základom pre dynamické špecifikátory `import()`. Pre súbory modulov použite `evalAsModule()`.

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

Ak sa súbor nedá prečítať, PHP vydá varovanie a `execFile()` vyvolá obyčajnú chybu `\RuntimeException` ("Cannot read file: …").

## setGlobal

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

Naviaže globálnu premennú viditeľnú vo všetkých nasledujúcich vyhodnoteniach JS. Hodnoty PHP sa prevedú na JS:

| typ PHP | typ JS |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (aj keď pomenúva funkciu, napríklad `'strlen'`) |
| pole (zoznam) | `Array` (kópia) |
| pole (asociatívne) | `Object` (kópia) |
| `Closure`, volateľný objekt, `[$object, 'method']` | `function` |
| akýkoľvek iný objekt, generátor alebo prostriedok | `undefined` |

Polia sa kopírujú: zmeny, ktoré v nich vykoná JS, sa v poli PHP neprejavia. Jednoduché objekty PHP zatiaľ nemožno odovzdať ([issue #18](https://github.com/inline0/phasis/issues/18)); alternatívne riešenia nájdete v časti [Zdieľané objekty](https://phasis.inline0.dev/sk/docs/interop/shared-objects).

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

V rámci JS:

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

## call

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

Zavolá globálnu funkciu JS s argumentmi PHP (prevedenými rovnako ako pri `setGlobal`). Návratová hodnota sa prevedie späť na PHP. Volanie nedefinovaného názvu vyvolá `Phasis\Exceptions\ReferenceError`; volanie globálnej premennej, ktorá nie je funkciou, vyvolá `Phasis\Exceptions\TypeError`.

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

## Výstup konzoly

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

`console.log`, `console.info`, `console.warn`, `console.error` a `console.debug` nič nevypisujú. Každé volanie pridá do vyrovnávacej pamäte enginu jeden naformátovaný riadok. `getConsoleLines()` vráti uložené riadky ako zoznam reťazcov, `getConsoleOutput()` ich vráti spojené pomocou `\n` a `clearConsole()` vyprázdni pamäť. Pamäť sa medzi volaniami `eval()` nevymaže.

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

Nastaví limit zdrojov, ktorý možno meniť za behu. Existuje presne jeden:

| Limit | Predvolená hodnota | Opis |
|---|---|---|
| `maxLoopIterations` | `100000` | maximálny počet iterácií na jedno spustenie slučky `for` / `while` / `do…while` v interpreteri so stromovým prechodom |

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

Každý iný názov sa potichu ignoruje; neexistujú limity dĺžky reťazca, výstupu konzoly ani času behu.

Limit slučky sa zatiaľ nevynucuje všade. Počet iterácií slučiek vykonávaných vo VM bajtkódu, ktorý pokrýva telá funkcií a programy najvyššej úrovne prijaté kompilátorom bajtkódu, sa nikdy nesleduje, takže `while (true) {}` beží, kým ho nezastaví PHP ([issue #19](https://github.com/inline0/phasis/issues/19)). Nespoliehajte sa naň pri zastavovaní nedôveryhodného kódu; použite PHP s `max_execution_time` alebo samostatný proces.

Po dosiahnutí limitu môže chybu zachytiť blok JS `try` / `catch` (ako `Error` so správou "Maximum loop iterations exceeded"). Ak ju nič nezachytí, dostane sa do PHP ako `Phasis\Exceptions\InternalError`, ktorý dedí od `Phasis\Exceptions\RuntimeError`, nie od `JsThrowable`.

V engine sú pevne nastavené aj dva ďalšie limity, ktoré nemožno meniť za behu: `Phasis\Runtime\CallStack::maxDepth` (4,096 rámcov JS) a ochranný limit hĺbky transpilo­vaných uzáverov JsToPhp (8,192 rámcov PHP). Pri prekročení oba vyvolajú "Maximum call stack size exceeded", ktorá sa prejaví aj v PHP ako `InternalError`. Tieto limity sú výrazne vyššie než pri akomkoľvek legitímnom programe JS — ich zvýšenie si vyžaduje zmenu kódu, nie nastavenia.

## reset

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

Vymaže všetky globálne premenné definované používateľom a vyrovnávaciu pamäť konzoly a znova vytvorí štandardnú knižnicu aj event loop.

V engine vytvorenom v predvolenom lenivom režime `reset()` momentálne odstráni všetky lenivo nainštalované vstavané funkcie: potom sú `Map`, `JSON`, `Math`, `console`, `fetch` a ostatné nedefinované ([issue #22](https://github.com/inline0/phasis/issues/22)). Kým sa to neopraví, vytvorte radšej novú inštanciu `Engine` alebo ju vytvorte s `eager: true`, kde `reset()` funguje.

## Event loop

Engine obsahuje vlastný event loop JavaScript s `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` a `queueMicrotask` na globálnom objekte. Mikrotasky (obslužné rutiny Promise, `queueMicrotask`) sa vyprázdnia medzi každou makroúlohou; časovače sa spúšťajú v poradí termínov pomocou frontu úloh pre každú realm.

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

`eval()` a `evalAsModule()` automaticky zavolajú `runEventLoop()` po dokončení synchrónneho kódu. Skript, ktorý naplánuje `setTimeout(cb, 5000)` na najvyššej úrovni, teda spustí `cb` ešte pred návratom `eval()` — loop používa **virtuálny čas** a posunie hodiny na najbližší čakajúci termín namiesto PHP. Zodpovedá sémantike `node script.js`.

Pri integrácii v reálnom čase (dlho bežiace servery, integrácia UI, plánovanie podľa skutočných hodín) spustí `tickEventLoop()` jednu iteráciu (vyprázdni mikrotasky, spustí všetky časovače, ktorých termín nastal) a vráti sa. `pendingTaskCount()` oznamuje zostávajúce časovače; integrátor si môže vytvoriť vlastný cyklus:

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

Nekonečne bežiaci `setInterval` má stanovený limit: `runEventLoop()` sa zastaví po 100,000 cykloch a vyvolá obyčajnú chybu `\RuntimeException` — chyba v kóde JS sa prejaví ako zrozumiteľná chyba PHP namiesto zablokovania. Interval zostáva naplánovaný, takže každé ďalšie volanie `eval()` v tom istom engine vyvolá rovnakú chybu; engine potom zahoďte. Nezachytené chyby v spätnom volaní časovača sa odošlú cez `error_log()` a loop pokračuje vo vyprázdňovaní frontu; jedna chybná úloha nezastaví program.

## Háčiky Fetch

Phasis štandardne obsahuje zapnutú funkciu `fetch()`, ktorá ako transport používa `ext-curl` z PHP. Tri háčiky umožňujú nahradiť transport, filtrovať požiadavky alebo zapnúť ukladanie súborov cookie. Každý prijíma volateľný prvok PHP alebo metódu objektu; pravidlá tvaru hodnôt nájdete v časti [Web API](https://phasis.inline0.dev/sk/docs/compatibility/web-apis).

### setFetchTransport

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

Nahradí predvolený CurlTransport. Volateľný prvok prijíma opisovač požiadavky a voliteľný `AbortSignal` JsObject; vracia opisovač odpovede.

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

Bežný vzor integrácie: v testoch nainštalovať falošný transport a v produkcii skutočný curl transport.

### setFetchPolicy

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

Háčik politiky pred odoslaním požiadavky. Prijíma `Request` JsObject; odmietne ju vyhodením výnimky, prepíše vrátením upraveného `Request` alebo povolí vrátením `null` či bez hodnoty.

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

Voliteľné úložisko súborov cookie. Prijíma ľubovoľný objekt so `get(string $url): string` a `set(string $url, string $header): void` — na strane PHP alebo JS.

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

Bez `setCookieJar()` `fetch` ignoruje prichádzajúce hlavičky `Set-Cookie` aj odchádzajúce hlavičky `Cookie` — predvolene sa nepoužívajú **žiadne súbory cookie**.

## Výnimky

| Trieda | Dedi od | Kedy |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | explicitné JS `throw`, ktoré nič nezachytilo |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | chyba vyvolaná samotným enginom (napríklad `null.x` alebo nedefinovaná premenná), ktorú nič nezachytilo |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | bol prekročený limit slučky alebo limit zásobníka volaní |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | základná trieda vyššie uvedených tried |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | zdrojový kód sa nedá parsovať |
| `\RuntimeException` | | `execFile()` nedokáže prečítať súbor; event loop dosiahol limit 100,000 cyklov |

Ak chcete zachytiť každú chybu skriptu, zachyťte `Phasis\Exceptions\RuntimeError` a `Phasis\Exceptions\SyntaxError` alebo jednoducho `\RuntimeException`.

Výnimky vyvolané vo vlastných hostiteľských funkciách PHP sú odlišné: v súčasnosti prechádzajú cez JS `try` / `catch` a dostanú sa nezmenené k volajúcemu `eval()`, pričom si zachovajú pôvodnú triedu ([problém č. 20](https://github.com/inline0/phasis/issues/20)). Pozrite si [Hostiteľské funkcie](https://phasis.inline0.dev/sk/docs/interop/host-functions#throwing-back-into-js).

## Životný cyklus

Vytvorenie `Engine` je nenáročné (za studena trvá približne niekoľko ms) a štandardná knižnica sa načítava až na požiadanie. Pri väčšine scenárov vloženia stačí jeden dlho žijúci engine na proces. Pri použití s viacerými nájomníkmi vytvorte pre každého nájomníka alebo požiadavku vlastný engine, aby medzi nimi neunikali globálne premenné. Vytvorenie nového enginu je v súčasnosti spoľahlivý spôsob, ako začať s čistým stavom (pozrite si [reset](#reset)).

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