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

Μετατροπή τιμών

Κάθε κλήση που διασχίζει το όριο PHP↔JS — setGlobal, call, μια συνάρτηση φιλοξενίας που καλείται από το JS — περνά από έναν μετατροπέα:

  • PHP → JS όταν οι τιμές φιλοξενίας εισέρχονται στη μηχανή (setGlobal(), ορίσματα call(), τιμές επιστροφής συναρτήσεων φιλοξενίας).
  • JS → PHP όταν οι τιμές JS επιστρέφουν προς τα έξω. Υπάρχουν δύο διαδρομές με ελαφρώς διαφορετικούς κανόνες: τα αποτελέσματα των eval() και call(), καθώς και τα ορίσματα που περνά το JS σε μια συνάρτηση φιλοξενίας.

Η μετατροπή δημιουργεί αντίγραφα. Οι πρωτογενείς τιμές αντιστοιχίζονται σε πρωτογενείς τιμές, οι πίνακες και τα αντικείμενα αντιγράφονται σε νέες δομές και οι κλήσιμες τιμές PHP τυλίγονται ως συναρτήσεις JS. Τίποτα δεν μοιράζεται με αναφορά, εκτός από τις ίδιες τις κλήσιμες τιμές.

PHP → JS

Τιμή PHP Αποτέλεσμα JS
null null
true / false true / false
int number (διπλής ακρίβειας IEEE 754· οι ακέραιοι πέρα από το 2^53 χάνουν ακρίβεια)
float number (διατηρούνται NaN, ±Infinity, ±0)
string string (τα byte UTF-8 PHP ερμηνεύονται ως μονάδες κώδικα UTF-16), ακόμη κι όταν αποτελεί το όνομα μιας συνάρτησης PHP
πίνακας λίστας array (κλειδιά 0, 1, 2, … με τη σειρά) νέος Array, τα στοιχεία μετατρέπονται αναδρομικά
οποιοδήποτε άλλο array νέο απλό Object, οι τιμές μετατρέπονται αναδρομικά
Closure, αντικείμενο με δυνατότητα κλήσης, κλήσιμος πίνακας όπως το [$object, 'method'] function που καλεί την κλήσιμη τιμή PHP
οποιοδήποτε άλλο αντικείμενο, Generator, πόρος undefined

Οι πίνακες με μικτά κλειδιά (τα [0 => 'a', 'name' => 'b'] του PHP) γίνονται απλά αντικείμενα, επειδή οι πίνακες στο JS πρέπει να έχουν συνεχόμενες ακέραιες θέσεις ευρετηρίου.

PHP
$engine->setGlobal('list', [1, 2, 3]);                 // → JS Array
$engine->setGlobal('map',  ['a' => 1, 'b' => 2]);      // → JS Object
$engine->setGlobal('mixed', [0 => 'x', 'k' => 'v']);   // → JS Object

Επειδή οι πίνακες αντιγράφονται, οι αλλαγές στο JS παραμένουν στο JS:

PHP
$list = [1, 2, 3];
$engine->setGlobal('list', $list);
$engine->eval('list.push(4)');
count($list);                 // 3
$engine->eval('list.length'); // 4

Τα αντικείμενα PHP που δεν είναι κλήσιμα δεν μετατρέπονται· φτάνουν ως undefined (issue #18). Μετατρέψτε τα πρώτα σε πίνακες, για παράδειγμα με το get_object_vars(), ή εκθέστε τις μεθόδους τους ως συναρτήσεις κλεισίματος. Δείτε τα Κοινόχρηστα αντικείμενα.

JS → PHP: αποτελέσματα eval() και call()

Τιμή JS Αποτέλεσμα PHP
undefined, null null
boolean bool
number (ακέραιος, εντός ορίων int) int (το -0 γίνεται 0)
NaN, Infinity, -Infinity NAN, INF, -INF
number (άλλο) float
string string (UTF-8)
bigint, symbol null
function null
Array πίνακας array, τα στοιχεία μετατρέπονται αναδρομικά
TypedArray πίνακας array αριθμών
οποιοδήποτε άλλο αντικείμενο (απλά αντικείμενα, Error, Date, Map, Set, Promise, ArrayBuffer, …) συσχετιστικός array των δικών του ιδιοτήτων δεδομένων, παραλείποντας τους προσπελαστές και τις ιδιότητες με τιμή συνάρτησης

Η τελευταία γραμμή σημαίνει ότι τα αντικείμενα των οποίων η κατάσταση βρίσκεται σε εσωτερικές υποδοχές μετατρέπονται σε κενό πίνακα: τα new Date(0), new Map([["a", 1]]) και Promise.resolve(5) επιστρέφουν όλα []. Ένα Error επιστρέφει τα δικά του message και stack. Ένα αντικείμενο που αναφέρεται στον εαυτό του μετατρέπει τις εσωτερικές αναφορές του σε null.

PHP
$engine->eval('[1, 2, 3]');                // [1, 2, 3]
$engine->eval('({a: 1, b: 2})');           // ['a' => 1, 'b' => 2]
$engine->eval('new Uint8Array([1, 2, 3])'); // [1, 2, 3]
$engine->eval('1234567890123456789n');     // null
$engine->eval('new Date(0)');              // []

Μετατρέψτε τις τιμές μέσα στο JS όταν τις χρειάζεστε στο PHP:

PHP
$engine->eval('(1234567890123456789n).toString()');  // "1234567890123456789"
$engine->eval('new Date(0).toISOString()');           // "1970-01-01T00:00:00.000Z"
$engine->eval('Object.fromEntries(new Map([["a", 1]]))'); // ['a' => 1]

JS → PHP: ορίσματα συναρτήσεων φιλοξενίας

Τα ορίσματα που περνά το JS σε μια συνάρτηση φιλοξενίας PHP ακολουθούν τους ίδιους κανόνες για πρωτογενείς τιμές, πίνακες και απλά αντικείμενα, με τις εξής διαφορές:

Τιμή JS Όρισμα PHP
TypedArray, ArrayBuffer, DataView το εγγενές αντικείμενο JsValue της μηχανής, χωρίς μετατροπή
αντικείμενο συσχετιστικός array όλων των δικών του ιδιοτήτων, μαζί με τους προσπελαστές (εκτελούνται οι μέθοδοι λήψης τους) και τις ιδιότητες με τιμή συνάρτησης
συνάρτηση βέλους ένα array (συνήθως κενό)
function που έχει δηλωθεί με τη λέξη-κλειδί function, ή οποιοδήποτε αντικείμενο αναφέρεται στον εαυτό του καμία μετατροπή: η αντιγραφή δεν τερματίζεται ποτέ και το PHP εξαντλεί τη μνήμη (issue #21)

Προς το παρόν δεν υπάρχει τρόπος να λάβετε μια επανάκληση JS σε συνάρτηση φιλοξενίας. Περάστε δεδομένα, όχι συναρτήσεις, μέσα από αυτό το όριο.

Αριθμοί και ακρίβεια

Οι αριθμοί JavaScript είναι διπλής ακρίβειας IEEE 754. Οι ακέραιοι PHP είναι εγγενείς στην πλατφόρμα (64 bit σε κάθε σύγχρονη έκδοση). Η μετατροπή διατηρεί ακριβώς τους ακεραίους έως ±2^53· οι μεγαλύτεροι ακέραιοι PHP γίνονται number και χάνουν ακρίβεια πέρα από τα 16 δεκαδικά ψηφία. Χρησιμοποιήστε το BigInt μέσα στο JS αν χρειάζεστε ακριβείς πράξεις με μεγαλύτερους ακεραίους και περάστε το αποτέλεσμα ως συμβολοσειρά.

Συμβολοσειρές

Οι συμβολοσειρές PHP είναι πίνακες byte· οι συμβολοσειρές JS είναι ακολουθίες μονάδων κώδικα UTF-16. Η μηχανή τις γεφυρώνει ερμηνεύοντας τα byte PHP ως UTF-8 και κωδικοποιώντας τα ξανά εσωτερικά σε UTF-16. Αυτό γίνεται χωρίς απώλειες για έγκυρη είσοδο UTF-8. Τα byte που δεν αποτελούν έγκυρο UTF-8 περνούν αυτούσια, αντί να αντικαθίστανται από U+FFFD.

Η String.length, η πρόσβαση με δείκτη (s[0]) και η κατάτμηση μετρούν μονάδες κώδικα UTF-16 σύμφωνα με την προδιαγραφή ECMAScript — όχι σημεία κώδικα ούτε byte. Επομένως, οι συμβολοσειρές που περιέχουν χαρακτήρες εκτός βασικού πολυγλωσσικού επιπέδου, όπως το '🍕', έχουν .length === 2 (τα δύο μισά υποκατάστασης του U+1F355).

Σταθερότητα μετά την επιστροφή

Τα απλά δεδομένα διατηρούνται σε μια πλήρη διαδρομή μετάβασης και επιστροφής: μια τιμή PHP που αποτελείται από βαθμωτές τιμές, λίστες και συσχετιστικούς πίνακες, όταν περνά μέσω του setGlobal() και επιστρέφει μέσω του eval() ή του call(), παράγει την ίδια δομή, με εξαίρεση ότι οι ακέραιες τιμές κινητής υποδιαστολής επιστρέφουν ως int (το 1.0 επιστρέφει 1).

Οτιδήποτε άλλο στους παραπάνω πίνακες που αντιστοιχίζεται σε null, undefined ή [] δεν διατηρείται σε μια πλήρη διαδρομή μετάβασης και επιστροφής.