
# API

Jedinou třídou rozhraní pro spouštění API je `Phasis\Engine`. Všechno ostatní (`JsValue`, `JsFunction`, `JsObject`, …) je interní součástí enginu a zpřístupňují se pouze hostitelským funkcím, které propojují PHP↔JS. Jediným dalším veřejným rozhraním je samostatný [parser API](https://phasis.inline0.dev/cs/docs/parser) (třídy `Phasis\Parser\Parser` a `Phasis\Ast`), který parsuje zdrojový kód bez jeho spuštění.

## Vytvoření

```php
use Phasis\Engine;

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

Nová instance `Engine` zpřístupňuje úplnou standardní knihovnu — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` i základní jazykové primitivy se instalují okamžitě, aby fungovala specifikace (bez `Function.prototype` nelze mít třídy, bez `Promise` async/await a bez `RegExp` regulární literály). Všechno ostatní — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` a skupina array-buffer, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, skupina weak-ref, `Temporal`, `ShadowRealm`, Web Platform Pack a Fetch Pack — se registruje jako zástupný přístupor a vytvoří se až při prvním čtení. Čtení globální proměnné je transparentní: fungují `Map`, `typeof Map`, `'Map' in globalThis` i `Object.keys(globalThis).includes('Map')` a po prvním čtení zástupný objekt nahradí běžný deskriptor datové vlastnosti.

Pokud výslovně potřebujete chování před zavedením líného načítání — tedy instalaci všech vestavěných funkcí při vytvoření a plné rozhraní deskriptorů dostupné při introspekci — předejte `eager: true`. Testovací prostředí pro ověření shody (`bin/test262`, `bin/wpt`) vytváří enginy v režimu eager, aby test262/WPT viděly očekávané tvary deskriptorů.

## eval

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

Parsuje a spustí `$source` jako klasický skript v globálním oboru enginu a poté spouští smyčku událostí, dokud není prázdná. Vrátí hodnotu posledního vyhodnoceného výrazu převedenou na PHP (nebo `null`, pokud program nekončil výrazem). Pravidla najdete v části [Převod hodnot](https://phasis.inline0.dev/cs/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 se nerozbalí: `eval('Promise.resolve(5)')` vrátí prázdné pole. Protože se smyčka událostí vyprázdní dřív, než se `eval()` vrátí, uložte vyřešenou hodnotu do globální proměnné a přečtěte ji druhým voláním `eval()`:

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

Pokud explicitní JS `throw` nic nezachytí, projeví se jako `Phasis\Exceptions\JsThrowable`, jehož zpráva zní `"Name: message"` a vlastnost `$jsValue` obsahuje vyvolaný `JsValue`. Chyby vyvolané samotným enginem, například při čtení vlastnosti objektu `null` nebo nedefinované proměnné, se projeví jako `Phasis\Exceptions\TypeError`, `ReferenceError` nebo `RangeError`. Všechny tyto chyby dědí z `Phasis\Exceptions\RuntimeError`; chyby parsování dědí z `Phasis\Exceptions\SyntaxError`. Viz [Výjimky](#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` jako modul ES: jsou dostupné `import` / `export` i nejvyšší úroveň `await` a `import.meta`; relativní specifikátory se vyhodnocují vzhledem k `$path` (absolutní cestě k souboru). Smyčku událostí spustí stejně jako `eval()` a vždy vrátí `null`; výsledky čtěte přes globální proměnné nebo vyrovnávací paměť konzole.

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

Zavaděč rozlišuje pouze relativní a absolutní cesty; známá omezení najdete v části [Známá omezení](https://phasis.inline0.dev/cs/docs/compatibility/limitations#module-loader).

## execFile

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

Načte soubor z disku a předá jeho obsah `eval()`, takže se spustí jako klasický skript bez ohledu na příponu; soubor s `import` / `export` skončí chybou SyntaxError. Skutečná cesta k souboru se použije jako základ pro dynamické specifikátory `import()`. Pro soubory modulů použijte `evalAsModule()`.

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

Pokud soubor nelze přečíst, PHP zobrazí varování a `execFile()` vyvolá běžnou chybu `\RuntimeException` („Nelze přečíst soubor: …“).

## setGlobal

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

Naváže globální proměnnou viditelnou při všech následujících vyhodnoceních JS. Hodnoty PHP se převedou na JS:

| Typ PHP | Typ JS |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (i když označuje funkci, například `'strlen'`) |
| pole (seznam) | `Array` (kopie) |
| pole (asociativní) | `Object` (kopie) |
| `Closure`, volatelný objekt, `[$object, 'method']` | `function` |
| jakýkoli jiný objekt, generátor nebo prostředek | `undefined` |

Pole se kopírují: změny, které v nich provede JS, se v poli PHP neprojeví. Prosté objekty PHP zatím nelze předávat ([problém #18](https://github.com/inline0/phasis/issues/18)); možnosti náhradního řešení najdete v části [Sdílené objekty](https://phasis.inline0.dev/cs/docs/interop/shared-objects).

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

Uvnitř 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ální funkci JS s argumenty PHP (převedenými stejně jako v `setGlobal`). Návratová hodnota se převede zpět na PHP. Volání nedefinovaného názvu vyvolá `Phasis\Exceptions\ReferenceError`; volání globální proměnné, která není funkcí, vyvolá `Phasis\Exceptions\TypeError`.

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

## Výstup konzole

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

Volání `console.log`, `console.info`, `console.warn`, `console.error` a `console.debug` nic nevypisují. Každé volání přidá do vyrovnávací paměti enginu jeden formátovaný řádek. `getConsoleLines()` vrátí řádky z paměti jako seznam řetězců, `getConsoleOutput()` je vrátí spojené pomocí `\n` a `clearConsole()` paměť vyprázdní. Paměť se mezi voláními `eval()` nemaž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 prostředků, který lze měnit za běhu. Existuje právě jeden:

| Limit | Výchozí hodnota | Popis |
|---|---|---|
| `maxLoopIterations` | `100000` | maximální počet iterací při každém spuštění smyčky `for` / `while` / `do…while` v interpretu, který prochází strom |

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

Jiné názvy se tiše ignorují; neexistují limity délky řetězce, výstupu konzole ani doby běhu.

Limit smyčky se zatím nevynucuje všude. Smyčky běžící ve VM pro bajtový kód — ta pokrývá těla funkcí a programy nejvyšší úrovně, které kompilátor bajtového kódu přijme — se nikdy nepočítají, takže `while (true) {}` běží, dokud jej nezastaví PHP ([problém #19](https://github.com/inline0/phasis/issues/19)). Nespoléhejte na tento limit při zastavování nedůvěryhodného kódu; použijte `max_execution_time` PHP nebo samostatný proces.

Při dosažení limitu může chybu zachytit JS `try` / `catch` (jako `Error` se zprávou „Maximum loop iterations exceeded“). Pokud ji nic nezachytí, dostane se do PHP jako `Phasis\Exceptions\InternalError`, který dědí z `Phasis\Exceptions\RuntimeError`, nikoli z `JsThrowable`.

Ve stroji jsou pevně nastaveny další dva limity, které nelze měnit za běhu: `Phasis\Runtime\CallStack::maxDepth` (4,096 JS rámců) a limit hloubky transpileovaných uzávěrů JsToPhp (8,192 PHP rámců). Při překročení obou dojde k chybě „Maximum call stack size exceeded“, která se také projeví v PHP jako `InternalError`. Jsou nastaveny výrazně nad úroveň, kterou může vyžadovat jakýkoli legitimní program v JS — jejich zvýšení vyžaduje změnu kódu, nejde o konfigurační volbu.

## reset

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

Vymaže všechny globální proměnné definované uživatelem a obsah konzole a znovu vytvoří standardní knihovnu i smyčku událostí.

U stroje vytvořeného ve výchozím líném režimu `reset()` v současnosti odstraní všechny líně instalované vestavěné funkce: poté jsou `Map`, `JSON`, `Math`, `console`, `fetch` i ostatní nedefinované ([issue #22](https://github.com/inline0/phasis/issues/22)). Než se to opraví, vytvořte místo toho nový `Engine` nebo jej vytvořte s `eager: true`, kde `reset()` funguje.

## Smyčka událostí

Stroj obsahuje vlastní smyčku událostí JavaScript s `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` a `queueMicrotask` na globálním objektu. Mik úlohy (obslužné funkce Promise, `queueMicrotask`) se zpracují mezi každou makro úlohou; časovače se spouštějí podle termínu prostřednictvím fronty úloh daného 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í synchronního kódu. Skript, který na nejvyšší úrovni naplánuje `setTimeout(cb, 5000)`, se tedy spustí `cb` ještě před návratem `eval()` — smyčka používá **virtuální čas** a posune hodiny k nejbližšímu nevyřízenému termínu, místo aby čekala PHP. Odpovídá sémantice `node script.js`.

Pro integraci v reálném čase (dlouho běžící servery, integrace UI, plánování podle skutečných hodin) provede `tickEventLoop()` jednu iteraci (zpracuje mik úlohy a spustí všechny časovače, jejichž termín nastal) a vrátí se. `pendingTaskCount()` uvádí počet nevyřízených časovačů; hostitelský program může řídit vlastní smyčku:

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

Nekonečně běžící `setInterval` má omezenou délku: `runEventLoop()` končí po 100,000 cyklech a vyvolá běžnou chybu `\RuntimeException` — chyba v kódu JS se tak projeví jako jasná chyba PHP, místo aby program zamrzl. Interval zůstane naplánovaný, takže každé další `eval()` na stejném stroji vyvolá stejnou chybu; poté stroj zahoďte. Nezachycené chyby ve zpětném volání časovače se předávají přes `error_log()` a smyčka pokračuje ve zpracování; chyba jednoho časovače program neukončí.

## Háčky Fetch

Phasis má ve výchozím nastavení povolené `fetch()`, které jako přenosovou vrstvu využívá `ext-curl` od PHP. Tři háčky umožňují hostitelskému programu změnit přenosovou vrstvu, řídit požadavky nebo povolit ukládání cookies. Každý přijímá volatelnou funkci PHP nebo metodu objektu; smlouvu pro tvar hodnoty najdete v části [Web APIs](https://phasis.inline0.dev/cs/docs/compatibility/web-apis).

### setFetchTransport

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

Nahradí výchozí CurlTransport. Volatelná funkce přijímá popis požadavku a volitelný JsObject `AbortSignal` a vrací popis odpovědi.

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

Běžný způsob použití při integraci: v testech nainstalujte náhradní přenosovou vrstvu a v produkci skutečný přenos přes curl.

### setFetchPolicy

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

Háček zásad pro kontrolu před odesláním požadavku. Přijímá JsObject `Request`; může vyvolat chybu a požadavek zamítnout, vrátit upravené `Request` a požadavek přepsat, nebo vrátit `null` či žádnou hodnotu a požadavek povolit.

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

Volitelné úložiště cookies. Přijímá libovolný objekt zpřístupňující `get(string $url): string` a `set(string $url, string $header): void` — na straně PHP nebo 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 příchozí `Set-Cookie` i odchozí hlavičky `Cookie` — ve výchozím nastavení se nepoužívají žádné cookies.

## Výjimky

| Třída | Rozšiřuje | Kdy |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | explicitní JS `throw`, kterou nic nezachytilo |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | chyba vyvolaná samotným strojem (například `null.x` nebo nedefinovaná proměnná), kterou nic nezachytilo |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | byl překročen limit smyčky nebo hloubky volání |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | základní třída výše uvedených tříd |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | zdrojový kód nelze parsovat |
| `\RuntimeException` | | `execFile()` nemůže přečíst soubor; smyčka událostí narazila na limit 100,000 cyklů |

Chcete-li zachytit všechna selhání skriptu, zachyťte `Phasis\Exceptions\RuntimeError` a `Phasis\Exceptions\SyntaxError` nebo jednoduše `\RuntimeException`.

Výjimky vyvolané vašimi vlastními hostitelskými funkcemi PHP se chovají jinak: v současnosti procházejí přes JS `try` / `catch` a beze změny dorazí k volajícímu `eval()`, včetně původní třídy ([issue #20](https://github.com/inline0/phasis/issues/20)). Viz [Hostitelské funkce](https://phasis.inline0.dev/cs/docs/interop/host-functions#throwing-back-into-js).

## Životní cyklus

`Engine` se vytváří rychle (za studena několik ms) a standardní knihovna se instaluje líně. Ve většině případů integrace stačí jeden dlouho běžící stroj pro každý proces. Při použití více klienty dejte každému klientovi nebo požadavku vlastní stroj, aby mezi nimi nedocházelo k úniku globálních proměnných. Spolehlivým způsobem, jak začít s čistým prostředím, je vytvořit nový stroj (viz [reset](#reset)).

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