Μετάβαση στο περιεχόμενο

Ξεκινώντας

Εγκατάσταση

Κέλυφος
composer require phasis/phasis

Το Phasis απαιτεί PHP 8.2 ή νεότερη έκδοση με τρεις επεκτάσεις, όλες απαραίτητες για το composer.json:

  • mbstring — χειρισμός συμβολοσειρών.
  • bcmath — αριθμητική BigInt και χειρισμός αριθμών με ακέραια ακρίβεια.
  • curl — η προεπιλεγμένη μεταφορά πίσω από το fetch().

Τα mbstring και curl είναι ενεργοποιημένα στις περισσότερες εκδόσεις. Το bcmath συχνά δεν είναι: οι επίσημες εικόνες Docker του php (php:8.x-cli, php:8.x-fpm) το παραλείπουν, ενώ ορισμένες διανομές το διαθέτουν σε ξεχωριστό πακέτο (php-bcmath). Χωρίς αυτό, το Composer αρνείται να εγκαταστήσει το Phasis. Σε ένα Dockerfile που βασίζεται στην επίσημη εικόνα, προσθέστε:

Dockerfile
RUN docker-php-ext-install bcmath

Εκτελέστε το php -m για να δείτε ποιες επεκτάσεις διαθέτει η έκδοσή σας.

Το ext-intl είναι προαιρετικό. Το Phasis λειτουργεί χωρίς αυτό, αλλά ολόκληρη η οικογένεια Intl.* (Collator, NumberFormat, DateTimeFormat, PluralRules, Locale, DisplayNames, ListFormat, RelativeTimeFormat, Segmenter, DurationFormat) και τα ημερολόγια Temporal εκτός ISO (hebrew, islamic, japanese, persian κ.λπ.) το χρειάζονται. Εγκαταστήστε το όποτε η εφαρμογή φιλοξενίας εκθέτει JS που αφορά μορφοποίηση με επίγνωση τοπικών ρυθμίσεων. Το ext-openssl είναι επίσης προαιρετικό: η κρυπτογράφηση AES-CBC, AES-CTR και AES-GCM του crypto.subtle το χρειάζεται, ενώ οι συναρτήσεις κατακερματισμού και HMAC λειτουργούν χωρίς αυτό.

Χωρίς exec, χωρίς FFI, χωρίς Node.js, χωρίς προσαρμοσμένη εγγενή επέκταση.

Εκτέλεση σεναρίου από το CLI

Κέλυφος
./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 εκτυπώνει ό,τι γράφει το σενάριο με το console.log· δεν εκτυπώνει την τιμή της τελευταίας έκφρασης.

Δείτε την αναφορά του CLI για το πλήρες σύνολο εντολών.

Ενσωμάτωση στο PHP

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 διατηρεί την καθολική του κατάσταση μεταξύ κλήσεων, επομένως οι επόμενες κλήσεις του eval() βλέπουν οτιδήποτε ορίσατε νωρίτερα.

Τα console.log και τα αντίστοιχά τους προσθέτουν δεδομένα σε ένα buffer στη μηχανή αντί να γράφουν στο stdout. Διαβάστε το με το getConsoleOutput() (μία συμβολοσειρά, με τις γραμμές ενωμένες με \n) ή το getConsoleLines() (λίστα γραμμών) και αδειάστε το με το clearConsole().

Η αρχική εκκίνηση είναι οικονομική: εγκαθίσταται εξαρχής μόνο ο πυρήνας που απαιτείται για τη φόρτωση των προδιαγραφών (Object, Function, Array, Promise, Symbol, BigInt, RegExp και οι τύποι σφαλμάτων). Τα Map, TypedArray, Temporal, Intl, URL, fetch, τα πλήρη πακέτα Web Platform / Fetch και όλα τα υπόλοιπα δημιουργούνται διαφανώς κατά την πρώτη ανάγνωση. Περάστε το new Engine(eager: true) για να εγκαταστήσετε κάθε ενσωματωμένο στοιχείο κατά τη δημιουργία — χρήσιμο για εργαλεία συμμόρφωσης που εξετάζουν περιγραφείς πριν τους διαβάσουν· σπάνια χρειάζεται για συνήθη ενσωμάτωση.

Κλήση συναρτήσεων JS από PHP

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

Το call() αναζητά τη συνάρτηση με βάση το όνομά της στο καθολικό αντικείμενο και την καλεί με τα παρεχόμενα ορίσματα PHP (που μετατρέπονται αυτόματα σε τιμές JS).

Έκθεση ενός εκτελέσιμου PHP ως συνάρτησης JS

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 τυλίγονται ως συναρτήσεις JS. Τα ορίσματά τους μετατρέπονται από JS σε PHP κατά την κλήση και οι τιμές επιστροφής τους μετατρέπονται ξανά από PHP σε JS.

Αντικείμενα PHP

Τα αντικείμενα PHP δεν μπορούν ακόμη να κοινοποιηθούν στο JavaScript. Η setGlobal() με ένα απλό αντικείμενο PHP συνδέει το undefined (παρακολουθήστε το στο ζήτημα #18). Περάστε τα δεδομένα ως πίνακες, οι οποίοι αντιγράφονται στο JS, και εκθέστε τη συμπεριφορά ως συναρτήσεις κλεισίματος:

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

Δείτε τα Κοινόχρηστα αντικείμενα για λεπτομέρειες και άλλες λύσεις.

Όρια πόρων

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

Το maxLoopIterations (προεπιλογή 100,000) είναι το μόνο όριο που ρυθμίζεται κατά την εκτέλεση και σήμερα εφαρμόζεται μόνο στον κώδικα που εκτελεί ο διερμηνευτής διάσχισης δέντρου. Οι βρόχοι που εκτελεί η εικονική μηχανή bytecode (σώματα συναρτήσεων και το μεγαλύτερο μέρος του κώδικα κορυφαίου επιπέδου) δεν μετρώνται, επομένως ένα όριο από μόνο του δεν σταματά το while (true) {} (ζήτημα #19). Όταν επιτευχθεί το όριο, το σφάλμα μπορεί να συλληφθεί στο JS· αν δεν συλληφθεί, φτάνει στο PHP ως Phasis\Exceptions\InternalError, υποκλάση του Phasis\Exceptions\RuntimeError.

Η μηχανή έχει επίσης σταθερό όριο στοίβας κλήσεων 4,096 πλαισίων (και φραγμό 8,192 πλαισίων για μεταγλωττισμένες συναρτήσεις κλεισίματος)· η υπέρβασή του προκαλεί το σφάλμα "Maximum call stack size exceeded", και πάλι ως InternalError. Δεν υπάρχει όριο χρόνου εκτέλεσης, επομένως εκτελέστε μη έμπιστο κώδικα κάτω από το δικό του max_execution_time του PHP ή σε ξεχωριστή διεργασία. Δείτε το API: setLimit.

Επόμενα βήματα