
# API

Yürütme API tek bir sınıftan oluşur: `Phasis\Engine`. Diğer her şey (`JsValue`, `JsFunction`, `JsObject`, …) motorun içindedir ve yalnızca PHP↔JS arasında köprü kuran ana bilgisayar işlevlerine açılır. Diğer tek genel arayüz, kaynak kodunu çalıştırmadan ayrıştıran bağımsız [ayrıştırıcıAPI](https://phasis.inline0.dev/tr/docs/parser)'dır (`Phasis\Parser\Parser` ve `Phasis\Ast` sınıfları).

## Oluşturma

```php
use Phasis\Engine;

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

Yeni bir `Engine`, standart kitaplığın tamamını kullanıma sunar — `Object`, `Array`, `String`, `Number`, `Symbol`, `BigInt`, `Promise`, `RegExp`, `Error` ve temel dil intrinsics bileşenleri hemen yüklenir, böylece spesifikasyon çalışır (`Function.prototype` olmadan sınıflar, `Promise` olmadan async/await, `RegExp` olmadan regex sabitleri kullanılamaz). Diğer her şey — `Math`, `JSON`, `Map`, `Set`, `Date`, `TypedArray` ve dizi arabellek ailesi, `Atomics`, `Proxy`, `Reflect`, `console`, `Intl`, weak-ref ailesi, `Temporal`, `ShadowRealm`, Web Platform Pack ve Fetch Pack — yer tutucu erişimci olarak kaydedilir ve ilk okumada oluşturulur. Genel değişkeni okumak şeffaf biçimde gerçekleşir: `Map`, `typeof Map`, `'Map' in globalThis`, `Object.keys(globalThis).includes('Map')` hepsi çalışır ve ilk okumadan sonra yer tutucu, normal bir veri tanımlayıcısıyla değiştirilir.

Özellikle tembel yükleme öncesindeki davranış gerekiyorsa `eager: true` parametresini iletin — tüm yerleşik öğeler oluşturma sırasında kurulur ve tam tanımlayıcı yüzeyi iç gözlem için görünür olur. Uygunluk test düzeneği (`bin/test262`, `bin/wpt`), motorları hevesli modda oluşturur; böylece test262/WPT beklenen tanımlayıcı biçimlerini görür.

## eval

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

`$source` öğesini motorun global kapsamındaki klasik bir betik olarak ayrıştırıp çalıştırır, ardından olay döngüsünü boşalana kadar yürütür. Son değerlendirilen ifadenin PHP türüne dönüştürülmüş değerini döndürür (programın sonunda ifade yoksa `null`). Kurallar için [Değer dönüştürme](https://phasis.inline0.dev/tr/docs/interop/values) bölümüne bakın.

```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 açımlanmaz: `eval('Promise.resolve(5)')` boş bir dizi döndürür. `eval()` döndüğünde olay döngüsü tamamlanmış olduğundan, sonuçlanan değeri bir globale kaydedip ikinci bir `eval()` ile okuyun:

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

Hiçbir şey tarafından yakalanmayan açık bir JS `throw`, mesajı `"Name: message"` olan ve `$jsValue` özelliğinde fırlatılan `JsValue` değerini tutan bir `Phasis\Exceptions\JsThrowable` olarak görünür. Motorun kendisinin oluşturduğu hatalar (örneğin `null` özelliğini okumak veya tanımlanmamış bir değişken kullanmak) bunun yerine `Phasis\Exceptions\TypeError`, `ReferenceError` veya `RangeError` olarak görünür. Bunların tümü `Phasis\Exceptions\RuntimeError` sınıfını genişletir; ayrıştırma hataları ise `Phasis\Exceptions\SyntaxError` sınıfını genişletir. [İstisnalar](#exceptions) bölümüne bakın.

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

`$source` öğesini bir ES modülü olarak değerlendirir: `import` / `export`, üst düzey `await` ve `import.meta` kullanılabilir; göreli belirteçler `$path` (mutlak dosya yolu) temel alınarak çözümlenir. Olay döngüsünü `eval()` ile aynı şekilde çalıştırır ve her zaman `null` döndürür; sonuçları globaller veya konsol arabelleği üzerinden okuyun.

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

Yükleyici yalnızca göreli ve mutlak yolları çözümler; [Bilinen sınırlamalar](https://phasis.inline0.dev/tr/docs/compatibility/limitations#module-loader) bölümüne bakın.

## execFile

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

Diskten dosya okur ve içeriğini `eval()` öğesine iletir; bu nedenle uzantısı ne olursa olsun klasik betik olarak çalışır. `import` / `export` içeren bir dosya SyntaxError hatası verir. Dosyanın gerçek yolu, dinamik `import()` belirteçleri için temel yol olur. Modül dosyaları için `evalAsModule()` kullanın.

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

Dosya okunamazsa PHP bir uyarı verir ve `execFile()` sıradan bir `\RuntimeException` fırlatır ("Cannot read file: …").

## setGlobal

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

Sonraki tüm JS değerlendirmelerinde görülebilen bir global değişken bağlar. PHP değerleri JS türüne dönüştürülür:

| PHP türü | JS türü |
|---|---|
| `null` | `null` |
| `bool` | `boolean` |
| `int` / `float` | `number` |
| `string` | `string` (işlev adı olsa bile, örneğin `'strlen'`) |
| dizi (liste) | `Array` (kopya) |
| dizi (eşlemeli dizi) | `Object` (kopya) |
| `Closure`, çağrılabilir nesne, `[$object, 'method']` | `function` |
| diğer nesneler, üreteçler veya kaynaklar | `undefined` |

Diziler kopyalanır: JS üzerinde yapılan değişiklikler PHP dizisinde görünmez. Sıradan PHP nesneleri henüz iletilemez ([issue #18](https://github.com/inline0/phasis/issues/18)); geçici çözümler için [Paylaşılan nesneler](https://phasis.inline0.dev/tr/docs/interop/shared-objects) bölümüne bakın.

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

JS içinde:

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

## call

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

Global bir JS işlevini PHP bağımsız değişkenle çağırır (`setGlobal` için kullanılan kurallara göre dönüştürülür). Dönüş değeri yeniden PHP türüne dönüştürülür. Tanımsız bir adın çağrılması `Phasis\Exceptions\ReferenceError` hatası fırlatır; işlev olmayan bir globalin çağrılması `Phasis\Exceptions\TypeError` hatası fırlatır.

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

## Konsol çıktısı

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

`console.log`, `console.info`, `console.warn`, `console.error` ve `console.debug` çıktı vermez. Her çağrı, motor üzerindeki bir arabelleğe biçimlendirilmiş tek bir satır ekler. `getConsoleLines()` arabellekteki satırları dizge listesi olarak döndürür, `getConsoleOutput()` bunları `\n` ile birleştirerek döndürür ve `clearConsole()` arabelleği boşaltır. Arabellek, `eval()` çağrıları arasında temizlenmez.

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

Çalışma zamanında ayarlanabilen bir kaynak sınırı belirler. Yalnızca bir tane vardır:

| Sınır | Varsayılan | Açıklama |
|---|---|---|
| `maxLoopIterations` | `100000` | ağaç dolaşan yorumlayıcının çalıştırdığı her `for` / `while` / `do…while` döngü çalıştırmasındaki en yüksek yineleme sayısı |

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

Diğer adlar sessizce yok sayılır; dizge uzunluğu, konsol çıktısı veya gerçek zaman için sınır yoktur.

Döngü sınırı henüz her yerde uygulanmaz. Bayt kodu VM'sinde çalışan döngüler (işlev gövdelerini ve bayt kodu derleyicisinin kabul ettiği üst düzey programları kapsar) hiç sayılmaz; dolayısıyla `while (true) {}`, PHP durdurana kadar çalışır ([issue #19](https://github.com/inline0/phasis/issues/19)). Güvenilmeyen kodu durduracağına güvenmeyin; PHP öğesinin `max_execution_time` özelliğini veya ayrı bir süreci kullanın.

Sınıra ulaşıldığında hata, bir JS `try` / `catch` tarafından yakalanabilir ("Maximum loop iterations exceeded" mesajını taşıyan bir `Error` olarak). Hiçbir şey yakalamazsa, PHP öğesine `Phasis\Exceptions\InternalError` olarak ulaşır; bu sınıf `Phasis\Exceptions\RuntimeError` sınıfını genişletir, `JsThrowable` sınıfını değil.

İki ek üst sınır motora sabitlenmiştir ve çalışma zamanında ayarlanamaz: `Phasis\Runtime\CallStack::maxDepth` (4,096 JS kare) ve JsToPhp dönüştürülmüş kapanış derinliği koruması (8,192 PHP kare). Her ikisi de aşıldığında "Maximum call stack size exceeded" hatasını fırlatır; bu da PHP öğesine `InternalError` olarak ulaşır. Her ikisi de meşru JS programlarının çok üzerinde ayarlanmıştır; artırmak bir yapılandırma seçeneği değil, kod değişikliğidir.

## reset

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

Kullanıcı tanımlı tüm globalleri ve konsol arabelleğini temizler; standart kütüphaneyi ve olay döngüsünü yeniden oluşturur.

Varsayılan tembel modda oluşturulmuş bir motorda `reset()` şu anda tembel yüklenen tüm yerleşik öğeleri kaybeder: sonrasında `Map`, `JSON`, `Math`, `console`, `fetch` ve diğerleri tanımsız olur ([issue #22](https://github.com/inline0/phasis/issues/22)). Bu düzeltilene kadar bunun yerine yeni bir `Engine` oluşturun veya `eager: true` ile oluşturun; bu durumda `reset()` çalışır.

## Olay döngüsü

Motor, global nesnede `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` ve `queueMicrotask` bulunan bir JavaScript olay döngüsüyle gelir. Mikro görevler (Promise işleyicileri, `queueMicrotask`), her makro görev arasında tamamlanır; zamanlayıcılar, her realm'e ait görev kuyruğunda son tarih sırasına göre çalışır.

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

`eval()` ve `evalAsModule()`, eşzamanlı kod tamamlandıktan sonra `runEventLoop()` öğesini otomatik olarak çağırır. Bu nedenle, en üst düzeyde `setTimeout(cb, 5000)` planlayan bir betik, `eval()` dönmeden önce `cb` çalıştırır; döngü uyumak PHP yerine saatini sonraki bekleyen son tarihe atlayarak **sanal zaman** kullanır. `node script.js` semantiğiyle eşleşir.

Gerçek zamanlı gömme (uzun ömürlü sunucular, UI entegrasyonu, gerçek saate göre zamanlama) için `tickEventLoop()` tek bir yineleme çalıştırır (mikro görevleri tamamlar, zamanı gelen zamanlayıcıları çalıştırır) ve döner. `pendingTaskCount()` bekleyen zamanlayıcıları bildirir; gömen uygulama kendi döngüsünü çalıştırabilir:

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

Kontrolden çıkan `setInterval` sınırlandırılır: `runEventLoop()` en fazla 100,000 döngü çalıştırır ve sıradan bir `\RuntimeException` fırlatır; JS kodunun hatası takılma yerine açık bir PHP hatası olarak görünür. Aralık zamanlanmış durumda kalır; bu nedenle aynı motor üzerindeki sonraki her `eval()` aynı hatayı fırlatır; sonrasında motoru atın. Zamanlayıcı geri çağrısındaki yakalanmamış hatalar `error_log()` üzerinden iletilir ve döngü görevleri tamamlamaya devam eder; hatalı bir zamanlayıcı programı durdurmaz.

## Fetch kancaları

Phasis, altta yatan aktarım katmanı olarak PHP öğesinin `ext-curl` özelliğiyle varsayılan olarak etkin `fetch()` sunar. Üç kanca, gömen uygulamanın aktarım katmanını değiştirmesine, istekleri denetlemesine veya çerez depolamasını etkinleştirmesine olanak tanır. Üçü de PHP çağrılabilirini veya nesne üzerindeki bir yöntemi kabul eder; değer biçimi sözleşmesi için [Web API'leri](https://phasis.inline0.dev/tr/docs/compatibility/web-apis) bölümüne bakın.

### setFetchTransport

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

Varsayılan CurlTransport'ı değiştirir. Çağrılabilir öğe bir istek tanımlayıcısı ve isteğe bağlı `AbortSignal` JsObject alır, ardından bir yanıt tanımlayıcısı döndürür.

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

Yaygın bir gömme örüntüsü: testlerde taklit aktarım katmanı, üretimde gerçek curl aktarım katmanı kurmak.

### setFetchPolicy

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

İstek öncesi politika kancası. `Request` JsObject'i alır; isteği reddetmek için hata fırlatabilir, yeniden yazmak için değiştirilmiş bir `Request` döndürebilir veya izin vermek için `null` / değer döndürmeyebilir.

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

İsteğe bağlı çerez deposu. `get(string $url): string` ve `set(string $url, string $header): void` öğelerini sunan herhangi bir nesneyi kabul eder — PHP tarafında veya JS tarafında.

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

`setCookieJar()` olmadan `fetch`, gelen `Set-Cookie` ve giden `Cookie` başlıklarını yok sayar; varsayılan davranış **çerez kullanmamaktır**.

## İstisnalar

| Sınıf | Genişlettiği | Durum |
|---|---|---|
| `Phasis\Exceptions\JsThrowable` | `RuntimeError` | Hiçbir şeyin yakalamadığı açık bir JS `throw` |
| `Phasis\Exceptions\TypeError`, `ReferenceError`, `RangeError` | `RuntimeError` | Motorun kendisinin oluşturduğu (örneğin `null.x` veya tanımsız değişken) ve hiçbir şeyin yakalamadığı hata |
| `Phasis\Exceptions\InternalError` | `RuntimeError` | Döngü sınırına veya çağrı yığını üst sınırına ulaşılması |
| `Phasis\Exceptions\RuntimeError` | `\RuntimeException` | Yukarıdakilerin temel sınıfı |
| `Phasis\Parser\ParseError` | `Phasis\Exceptions\SyntaxError` (`\RuntimeException`) | Kaynak kodunun ayrıştırılamaması |
| `\RuntimeException` | | `execFile()` dosyayı okuyamıyor; olay döngüsü 100,000 döngülük sınırına ulaştı |

Bir betikteki tüm hataları yakalamak için `Phasis\Exceptions\RuntimeError` ve `Phasis\Exceptions\SyntaxError` hatalarını yakalayın veya doğrudan `\RuntimeException` kullanın.

Kendi PHP ana bilgisayar işlevlerinizin fırlattığı istisnalar farklıdır: şu anda JS `try` / `catch` üzerinden olduğu gibi geçer ve özgün sınıflarıyla `eval()` çağrısına ulaşır ([issue #20](https://github.com/inline0/phasis/issues/20)). [Ana bilgisayar işlevleri](https://phasis.inline0.dev/tr/docs/interop/host-functions#throwing-back-into-js) bölümüne bakın.

## Yaşam döngüsü

`Engine` oluşturmak kolaydır (soğuk başlatmada ~birkaç ms) ve standart kütüphane tembel olarak yüklenir. Çoğu gömme senaryosunda süreç başına tek ve uzun ömürlü bir motor yeterlidir. Çok kiracılı kullanımda, globallerin kiracılar arasında sızmaması için her kiracıya veya isteğe ayrı bir motor verin. Bugün temiz bir başlangıç yapmanın güvenilir yolu yeni bir motor oluşturmaktır ([reset](#reset) bölümüne bakın).

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