
# API

O ponto de entrada de execução API consiste em uma única classe: `Phasis\Engine`. Todo o restante (`JsValue`, `JsFunction`, `JsObject`, …) é interno ao mecanismo e só fica exposto às funções hospedeiras que conectam PHP↔JS. A única outra interface pública é o [parserAPI](https://phasis.inline0.dev/pt/docs/parser) independente (`Phasis\Parser\Parser` e as classes `Phasis\Ast`), que analisa o código-fonte sem executá-lo.

## Construção

```php
use Phasis\Engine;

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

Um `Engine` recém-criado expõe a biblioteca padrão completa — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` e as intrínsecas da linguagem principal são instaladas imediatamente para que a especificação funcione (não há classes sem `Function.prototype`, async/await sem `Promise` nem literais de regex sem `RegExp`). Todo o restante — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` e a família de array buffers, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, a família weak-ref, `Temporal`, `ShadowRealm`, o Web Platform Pack e o Fetch Pack — é registrado como acessor substituto e materializado na primeira leitura. A leitura do global é transparente: `Map`, `typeof Map`, `'Map' in globalThis` e `Object.keys(globalThis).includes('Map')` funcionam, e após a primeira leitura o substituto é trocado por um descritor de dados normal.

Passe `eager: true` se precisar especificamente do comportamento anterior ao carregamento tardio — todos os recursos integrados são instalados na construção e a superfície completa dos descritores fica visível à introspecção. O harness de conformidade (`bin/test262`, `bin/wpt`) cria mecanismos no modo imediato para que test262/WPT vejam as formas de descritor esperadas.

## eval

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

Analisa e executa `$source` como um script clássico no escopo global do mecanismo e, em seguida, executa o loop de eventos até esvaziá-lo. Retorna o valor da última expressão avaliada, convertido para PHP (ou `null` se o programa não tiver uma expressão final). Consulte [Conversão de valores](https://phasis.inline0.dev/pt/docs/interop/values) para ver as regras.

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

Uma Promise não é desembrulhada: `eval('Promise.resolve(5)')` retorna um array vazio. Como o loop de eventos já foi esvaziado quando `eval()` retorna, armazene o valor resolvido em um global e leia-o com uma segunda chamada a `eval()`:

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

Uma exceção JS `throw` explícita que não é capturada aparece como `Phasis\Exceptions\JsThrowable`, cuja mensagem é `"Name: message"` e cuja propriedade `$jsValue` contém o valor `JsValue` lançado. Erros gerados pelo próprio mecanismo, como acessar uma propriedade de `null` ou usar uma variável indefinida, aparecem como `Phasis\Exceptions\TypeError`, `ReferenceError` ou `RangeError`. Todos eles estendem `Phasis\Exceptions\RuntimeError`; erros de análise sintática estendem `Phasis\Exceptions\SyntaxError`. Consulte [Exceções](#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
```

Avalia `$source` como um módulo ES: `import` / `export`, `await` e `import.meta` no nível superior ficam disponíveis, e os especificadores relativos são resolvidos com base em `$path` (um caminho absoluto de arquivo). Executa o loop de eventos como `eval()` e sempre retorna `null`; leia os resultados por meio dos globais ou do buffer do console.

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

O carregador resolve apenas caminhos relativos e absolutos; consulte [Limitações conhecidas](https://phasis.inline0.dev/pt/docs/compatibility/limitations#module-loader).

## execFile

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

Lê um arquivo do disco e passa seu conteúdo para `eval()`, que o executa como um script clássico, independentemente da extensão; um arquivo com `import` / `export` gera um SyntaxError. O caminho real do arquivo se torna a base para especificadores dinâmicos de `import()`. Use `evalAsModule()` para arquivos de módulo.

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

Se não for possível ler o arquivo, PHP emite um aviso e `execFile()` lança um `\RuntimeException` simples ("Não foi possível ler o arquivo: …").

## setGlobal

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

Vincula uma variável global visível em todas as avaliações subsequentes de JS. Os valores PHP são convertidos em JS:

| tipo PHP | tipo JS |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (mesmo quando nomeia uma função, como `'strlen'`) |
| array (lista) | `Array` (uma cópia) |
| array (associativo) | `Object` (uma cópia) |
| `Closure`, objeto invocável, `[$object, 'method']` | `function` |
| qualquer outro objeto, gerador ou recurso | `undefined` |

Os arrays são copiados: as alterações que JS fizer neles não aparecem no array PHP. Ainda não é possível passar objetos simples de PHP ([problema #18](https://github.com/inline0/phasis/issues/18)); consulte [Objetos compartilhados](https://phasis.inline0.dev/pt/docs/interop/shared-objects) para ver alternativas.

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

Dentro de JS:

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

## call

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

Chama uma função global de JS com argumentos PHP (convertidos como em `setGlobal`). O valor retornado é convertido novamente para PHP. Chamar um nome não definido gera `Phasis\Exceptions\ReferenceError`; chamar uma variável global que não seja uma função gera `Phasis\Exceptions\TypeError`.

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

## Saída do console

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

`console.log`, `console.info`, `console.warn`, `console.error` e `console.debug` não imprimem nada. Cada chamada acrescenta uma linha formatada a um buffer no mecanismo. `getConsoleLines()` retorna as linhas armazenadas como uma lista de strings, `getConsoleOutput()` as retorna unidas por `\n` e `clearConsole()` esvazia o buffer. O buffer não é limpo entre chamadas a `eval()`.

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

Define um limite de recursos ajustável durante a execução. Há exatamente um:

| Limite | Padrão | Descrição |
|---|---|---|
| `maxLoopIterations` | `100000` | máximo de iterações por execução de loop `for` / `while` / `do…while` pelo interpretador que percorre a árvore |

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

Qualquer outro nome é ignorado silenciosamente; não há limites para o comprimento das strings, a saída do console ou o tempo de execução.

O limite de loops ainda não é aplicado em todos os lugares. Loops executados na VM de bytecode, que cobre corpos de funções e programas de nível superior aceitos pelo compilador de bytecode, nunca são contabilizados; assim, `while (true) {}` é executado até que PHP o interrompa ([problema #19](https://github.com/inline0/phasis/issues/19)). Não confie nesse limite para interromper código não confiável; use PHP `max_execution_time` ou um processo separado.

Ao atingir o limite, o erro pode ser capturado por um JS `try` / `catch` (como um `Error` com a mensagem "Máximo de iterações de loop excedido"). Se nada o capturar, chega a PHP como `Phasis\Exceptions\InternalError`, que estende `Phasis\Exceptions\RuntimeError`, não `JsThrowable`.

Dois outros limites máximos estão embutidos no mecanismo e não podem ser ajustados durante a execução: `Phasis\Runtime\CallStack::maxDepth` (quadros 4,096 JS) e o limite de profundidade de closures transpiladas por JsToPhp (quadros 8,192 PHP). Ultrapassar qualquer um deles gera "Tamanho máximo da pilha de chamadas excedido", que também chega a PHP como `InternalError`. Esses limites ficam bem acima do necessário para qualquer programa JS legítimo — aumentá-los exige uma alteração no código, não um ajuste de configuração.

## reset

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

Remove todas as variáveis globais definidas pelo usuário e o buffer do console e recria a biblioteca padrão e o loop de eventos.

Em um mecanismo criado no modo tardio padrão, `reset()` atualmente perde todos os recursos integrados instalados sob demanda: depois disso, `Map`, `JSON`, `Math`, `console`, `fetch` e os demais ficam indefinidos ([problema #22](https://github.com/inline0/phasis/issues/22)). Até que isso seja corrigido, crie um novo `Engine` ou use `eager: true` na criação; assim, `reset()` funciona.

## Loop de eventos

O mecanismo inclui um JavaScript loop de eventos com `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` e `queueMicrotask` no objeto global. As microtarefas (handlers de Promise, `queueMicrotask`) são processadas entre cada macrotarefa; os temporizadores disparam por ordem de prazo usando uma fila de tarefas por realm.

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

`eval()` e `evalAsModule()` chamam `runEventLoop()` automaticamente quando o código síncrono termina. Assim, um script que agenda `setTimeout(cb, 5000)` no nível superior executa `cb` antes de `eval()` retornar — o loop usa **tempo virtual**, avançando o relógio até o próximo prazo pendente em vez de aguardar PHP. Corresponde à semântica de `node script.js`.

Para incorporação em tempo real (servidores de longa duração, integração com UI, agendamento com base em um relógio real), `tickEventLoop()` executa uma iteração (processa microtarefas, dispara os temporizadores vencidos) e retorna. `pendingTaskCount()` informa os temporizadores pendentes; o incorporador pode executar seu próprio loop:

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

Um `setInterval` descontrolado é limitado: `runEventLoop()` para após 100,000 ciclos e lança um `\RuntimeException` simples — a falha no código JS aparece como um erro PHP claro, em vez de travar. O intervalo continua agendado, então cada chamada posterior a `eval()` no mesmo mecanismo lança o mesmo erro; descarte o mecanismo depois disso. Erros não capturados em um callback de temporizador são emitidos por `error_log()`, e o loop continua processando tarefas; um temporizador com erro não encerra o programa.

## Hooks de Fetch

Phasis inclui `fetch()` ativado por padrão, usando `ext-curl` de PHP como transporte subjacente. Três hooks permitem que o incorporador troque o transporte, controle as solicitações ou ative o armazenamento de cookies. Os três aceitam um callable PHP ou um método de um objeto; consulte [APIs da Web](https://phasis.inline0.dev/pt/docs/compatibility/web-apis) para conhecer o contrato do formato dos valores.

### setFetchTransport

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

Substitui o CurlTransport padrão. O callable recebe um descritor de solicitação e um `AbortSignal` JsObject opcional; retorna um descritor de resposta.

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

Um padrão comum de incorporação: instalar um transporte simulado nos testes e o transporte curl real em produção.

### setFetchPolicy

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

Hook de política prévia à solicitação. Recebe o `Request` JsObject; pode lançar uma exceção para negar, retornar um `Request` modificado para reescrever ou retornar `null`/nenhum valor para permitir.

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

Armazenamento de cookies opcional. Aceita qualquer objeto que exponha `get(string $url): string` e `set(string $url, string $header): void` — do lado de PHP ou de 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());
```

Sem `setCookieJar()`, `fetch` ignora tanto os cabeçalhos `Set-Cookie` de entrada quanto os cabeçalhos `Cookie` de saída — o padrão é **sem cookies**.

## Exceções

| Classe | Estende | Quando |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | uma JS `throw` explícita que nada capturou |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | um erro gerado pelo próprio mecanismo (por exemplo, `null.x` ou uma variável indefinida) que nada capturou |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | o limite do loop ou o limite da pilha de chamadas foi ultrapassado |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | classe base das classes acima |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | o código-fonte não pode ser analisado |
| `\RuntimeException` | | `execFile()` não consegue ler o arquivo; o loop de eventos atingiu seu limite de 100,000 ciclos |

Para capturar toda falha de um script, capture `Phasis\Exceptions\RuntimeError` e `Phasis\Exceptions\SyntaxError` ou simplesmente `\RuntimeException`.

As exceções lançadas pelas suas próprias funções host de PHP são diferentes: atualmente, elas passam por JS `try` / `catch` e chegam inalteradas ao chamador de `eval()`, com a classe original ([problema #20](https://github.com/inline0/phasis/issues/20)). Consulte [Funções host](https://phasis.inline0.dev/pt/docs/interop/host-functions#throwing-back-into-js).

## Ciclo de vida

`Engine` é barato de construir (alguns ms em uma inicialização fria) e a biblioteca padrão é carregada de forma preguiçosa. Para a maioria dos cenários de incorporação, basta um mecanismo de longa duração por processo. Em ambientes multi-inquilino, dê a cada inquilino ou solicitação seu próprio mecanismo para que os globais nunca vazem entre eles. Hoje, construir um mecanismo novo é a forma confiável de começar com um estado limpo (consulte [redefinição](#reset)).

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