İçeriğe geç

Başlarken

Yükleme

Kabuk
composer require phasis/phasis

Phasis için, composer.json'nin gerektirdiği üç uzantıyla birlikte PHP 8.2 veya daha yenisi gerekir:

  • mbstring — dize işleme.
  • bcmath — BigInt aritmetiği ve tamsayı hassasiyetinde sayı işleme.
  • curl — fetch() arkasındaki varsayılan aktarım.

mbstring ve curl çoğu derlemede etkindir. bcmath ise çoğunlukla etkin değildir: resmi php Docker görüntülerinde (php:8.x-cli, php:8.x-fpm) bulunmaz ve bazı dağıtımlar bunu ayrı bir paket olarak sunar (php-bcmath). Bu olmadan Composer, Phasis'ın yüklenmesini reddeder. Resmi görüntüyü temel alan bir Dockerfile'a şunu ekleyin:

Dockerfile
RUN docker-php-ext-install bcmath

Derlemenizde hangi uzantıların bulunduğunu görmek için php -m komutunu çalıştırın.

ext-intl isteğe bağlıdır. Phasis onsuz çalışır; ancak Intl.* ailesinin tamamı (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) ve ISO olmayan Temporal takvimleri (hebrew, islamic, japanese, persian vb.) buna ihtiyaç duyar. Yerel ayarlara duyarlı biçimlendirmeye dokunan JS'ı barındıran uygulamalarda bunu yükleyin. ext-openssl de isteğe bağlıdır: AES-CBC, AES-CTR ve AES-GCM şifrelemesi için crypto.subtle gerekirken özetler ve HMAC onsuz çalışır.

exec yok, FFI yok, Node.js yok, özel yerel uzantı yok.

CLI üzerinden bir betik çalıştırma

Kabuk
./vendor/bin/phasis -e 'console.log(1 + 2 * 3)'
# 7

./vendor/bin/phasis -e 'console.log([1, 2, 3].map(x => x * x).join(","))'
# 1,4,9

./vendor/bin/phasis path/to/script.js

CLI, betiğin console.log ile yazdıklarını yazdırır; son ifadenin değerini yazdırmaz.

Tüm komutları görmek için CLI başvurusuna bakın.

PHP içine yerleştirme

PHP
<?php

require __DIR__ . '/vendor/autoload.php';

use Phasis\Engine;

$engine = new Engine();

// Evaluate an expression
$result = $engine->eval('1 + 2 * 3');
echo $result;
// 7

// Execute a script file
$engine->execFile('path/to/script.js');

// Bridge a PHP value into the JS global scope
$engine->setGlobal('config', ['debug' => true, 'version' => '1.0']);
$engine->eval('console.log(config.version)');

// console output is buffered on the engine, not printed
echo $engine->getConsoleOutput();
// 1.0

Engine örneği, çağrılar arasında küresel durumunu korur; böylece sonraki eval() çağrıları daha önce tanımladığınız her şeyi görür.

console.log ve benzerleri stdout'a yazmak yerine motor üzerindeki bir arabelleğe ekleme yapar. Arabelleği getConsoleOutput() ile (tek dize, satırlar \n ile birleştirilmiş) veya getConsoleLines() ile (satır listesi) okuyun; clearConsole() ile boşaltın.

Soğuk başlangıç maliyeti düşüktür: yalnızca belirtim yüklemesi için gerekli çekirdek (Object, Function, Array, Promise, Symbol, BigInt, RegExp ve hata türleri) başlangıçta yüklenir. Map, TypedArray, Temporal, Intl, URL, fetch, Web Platform / Fetch Packs'in tamamı ve diğer her şey ilk okumada şeffaf biçimde yüklenir. Yapıcı sırasında tüm yerleşik özellikleri yüklemek için new Engine(eager: true) değerini geçin; bu, tanımlayıcıları okumadan önce inceleyen uyumluluk araçları için kullanışlıdır, normal gömme işlemlerinde nadiren gerekir.

JS işlevlerini PHP üzerinden çağırma

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

call(), işlevi global nesnede adına göre bulur ve sağlanan PHP bağımsız değişkenlerle çağırır (otomatik olarak JS değerlerine dönüştürülür).

Bir PHP çağrılabilirini JS işlevi olarak sunma

PHP
$engine->setGlobal('fetchData', function (string $url): array {
    return json_decode(file_get_contents($url), true);
});

$engine->eval('const users = fetchData("https://api.example.com/users")');

PHP closure'ları, JS işlevleri olarak sarmalanır. Çağrıldıklarında bağımsız değişkenleri JS'dan PHP'a dönüştürülür; dönüş değerleri ise PHP'ten JS'e geri dönüştürülür.

PHP nesneleri

PHP nesneleri henüz JavaScript ile paylaşılamaz. Düz bir PHP nesnesiyle setGlobal(), undefined'ı bağlar (issue #18 başlığında izleniyor). Verileri JS içine kopyalanan diziler olarak aktarın ve davranışı closure'lar olarak sunun:

PHP
class Counter {
    public int $value = 0;
    public function increment(): void { $this->value++; }
}

$counter = new Counter();
$engine->setGlobal('increment', fn () => $counter->increment());

$engine->eval('increment(); increment();');

echo $counter->value;
// 2

Ayrıntılar ve diğer geçici çözümler için Paylaşılan nesneler bölümüne bakın.

Kaynak sınırları

PHP
$engine = new Engine();
$engine->setLimit('maxLoopIterations', 100_000);

maxLoopIterations (varsayılanı 100,000), çalışma zamanında ayarlanabilen tek sınırdır ve şu an yalnızca ağaç üzerinde yürüyen yorumlayıcının çalıştırdığı kod için geçerlidir. Bayt kodu VM'nin çalıştırdığı döngüler (işlev gövdeleri ve üst düzey kodun çoğu) sayılmaz; bu nedenle tek başına bir sınır while (true) {}'ı durdurmaz (issue #19). Sınıra ulaşıldığında hata JS içinde yakalanabilir; yakalanmazsa PHP'e Phasis\Exceptions\RuntimeError alt sınıfı olan Phasis\Exceptions\InternalError olarak ulaşır.

Motorun ayrıca sabit bir 4,096 çerçeveli çağrı yığını tavanı (ve transpile edilmiş closure'lar için 8,192 çerçeveli bir koruması) vardır; bu sınır aşılırsa yine InternalError olarak "Maximum call stack size exceeded" hatası fırlatılır. Duvar saati sınırı yoktur; bu nedenle güvenilmeyen kodu PHP'ın kendi max_execution_time'ı altında veya ayrı bir süreçte çalıştırın. Bkz. API: setLimit.

Sonraki adımlar