
# Συναρτήσεις υποδοχής

Οποιοδήποτε κλείσιμο PHP ή κλήσιμη συνάρτηση που δίνεται στη μηχανή μέσω του `setGlobal()` γίνεται κλήσιμη συνάρτηση JS. Δεν χρειάζεται χειροκίνητη καταχώριση, σχήμα ή μετατροπή δεδομένων — απλώς περάστε την κλήσιμη συνάρτηση.

## Βασικά

```php
$engine->setGlobal('greet', function (string $name): string {
    return "Hello, $name!";
});

echo $engine->eval('greet("world")');
// Hello, world!
```

Όταν το `greet("world")` εκτελείται στο JS:

1. Η συμβολοσειρά `"world"` μετατρέπεται από JS σε PHP.
2. Καλείται το κλείσιμο PHP με το μετατρεμμένο όρισμα.
3. Η τιμή επιστροφής μετατρέπεται από PHP ξανά σε JS.

Η μετατροπή ακολουθεί τους [κανόνες μετατροπής τιμών](https://phasis.inline0.dev/el/docs/interop/values). Τα ορίσματα είναι αντίγραφα: ένας πίνακας ή αντικείμενο JS φτάνει ως νέος πίνακας PHP, και οι αλλαγές του στο PHP δεν αλλάζουν την τιμή JS.

## Αριθμός ορισμάτων και παράμετροι

Η μηχανή περνά τα ορίσματα JS στην κλήσιμη συνάρτηση με τη σειρά, όπως είναι· δεν εξετάζει την υπογραφή. Οι συνηθισμένες συναρτήσεις αγνοούν τα επιπλέον ορίσματα, ενώ οι μεταβλητές παράμετροι τα συλλέγουν. Τα ορίσματα που λείπουν δεν συμπληρώνονται: αν το JS περάσει λιγότερα από όσα απαιτεί η συνάρτηση, το PHP προκαλεί `ArgumentCountError`. Ορίστε προεπιλεγμένες τιμές για τις προαιρετικές παραμέτρους.

```php
$engine->setGlobal('format', function (string $template, ...$args): string {
    return vsprintf($template, $args);
});

echo $engine->eval('format("%s is %d years old", "Ada", 36)');
// Ada is 36 years old
```

## Μετατροπή τύπων

Τα ορίσματα μετατρέπονται σε PHP με βάση τους κανόνες JS→PHP. Η μηχανή καλεί την κλήσιμη συνάρτησή σας από κώδικα που δηλώνει `strict_types=1`, επομένως το PHP ελέγχει αυστηρά τους τύπους παραμέτρων scalar και δεν κάνει εξαναγκασμό τύπου: ένας αριθμός JS που δίνεται σε παράμετρο `string` προκαλεί `TypeError`. Όπως κάθε εξαίρεση PHP από συνάρτηση υποδοχής, φτάνει στον καλούντα του `eval()` και όχι στο JS (βλ. παρακάτω).

```php
$engine->setGlobal('len', fn(string $s): int => strlen($s));

$engine->eval('len(42)');  // PHP TypeError: Argument #1 ($s) must be of type string, int given
```

Κάντε χειροκίνητα μετατροπή τύπου μέσα στο κλείσιμο (`(string) $value`) ή χρησιμοποιήστε τύπους ένωσης (`int|string`).

## Ρίψη εξαιρέσεων πίσω στο JS

Οι συνηθισμένες εξαιρέσεις PHP που προκαλούνται μέσα σε συνάρτηση υποδοχής **δεν** μετατρέπονται σήμερα σε σφάλματα JS. Περνούν από κάθε χειριστή JS `try` / `catch` και `.catch()` και φτάνουν στον κώδικα PHP που κάλεσε το `eval()` ή το `call()`, διατηρώντας την αρχική τους κλάση ([ζήτημα #20](https://github.com/inline0/phasis/issues/20)). Αυτό περιλαμβάνει τα `\Exception`, `\RuntimeException`, `\DivisionByZeroError` και τα `\TypeError` και `\ArgumentCountError` PHP που προκαλούνται από μη έγκυρα ορίσματα.

```php
$engine->setGlobal('divide', function (int $a, int $b): int {
    if ($b === 0) {
        throw new \DivisionByZeroError('cannot divide by zero');
    }
    return intdiv($a, $b);
});

try {
    $engine->eval('
        try { divide(10, 0); } catch (e) { /* never runs */ }
    ');
} catch (\DivisionByZeroError $e) {
    echo $e->getMessage();  // cannot divide by zero
}
```

Για να προκαλέσετε σφάλμα που μπορεί να πιάσει ο κώδικας JS, ρίξτε μία από τις κλάσεις σφαλμάτων της μηχανής: τα `Phasis\Exceptions\TypeError`, `RangeError` ή `ReferenceError` εμφανίζονται στο JS ως αντίστοιχοι τύποι σφάλματος JS, ενώ κάθε άλλο `Phasis\Exceptions\RuntimeError` εμφανίζεται ως απλό `Error`, όλα με το μήνυμά σας.

```php
use Phasis\Exceptions\RangeError;

$engine->setGlobal('divide', function (int $a, int $b): int {
    if ($b === 0) {
        throw new RangeError('cannot divide by zero');
    }
    return intdiv($a, $b);
});

$engine->eval('
    try {
        divide(10, 0);
    } catch (e) {
        console.log(e.name, e.message);  // RangeError cannot divide by zero
    }
');
```

## Επιστροφή σύνθετων τιμών

Η τιμή επιστροφής μετατρέπεται σύμφωνα με τους κανόνες PHP→JS. Για να επιστρέψετε αντικείμενο JS literal, επιστρέψτε συσχετιστικό πίνακα PHP:

```php
$engine->setGlobal('lookup', function (string $key): array {
    return ['key' => $key, 'value' => 42, 'meta' => ['ts' => time()]];
});

$engine->eval('
    const r = lookup("answer");
    console.log(r.value, r.meta.ts);
');
```

Για να επιστρέψετε πίνακα JS, επιστρέψτε διαδοχική λίστα PHP:

```php
$engine->setGlobal('range', fn(int $a, int $b): array => range($a, $b));

$engine->eval('range(1, 5).reduce((a, b) => a + b)');  // 15
```

Η επιστροφή αντικειμένου PHP, συμπεριλαμβανομένου ενός promise PHP από το ReactPHP, το Amp ή το Guzzle, δίνει JS `undefined` (βλ. [Μετατροπή τιμών](https://phasis.inline0.dev/el/docs/interop/values)).

## Ασύγχρονες συναρτήσεις υποδοχής

Οι συναρτήσεις υποδοχής είναι σύγχρονες. Ένα promise PHP ή άλλο thenable αντικείμενο δεν αναμένεται: μετατρέπεται σε `undefined`, οπότε το `await hostFn()` επιλύεται σε `undefined`. Εκτελέστε την εργασία σύγχρονα και επιστρέψτε το αποτέλεσμα· ο κώδικας JS μπορεί και πάλι να κάνει `await` την κλήση, η οποία τυλίγει την τιμή σε επιλυμένο promise:

```php
$engine->setGlobal('loadBody', function (string $url): string {
    return file_get_contents($url);
});

$engine->eval('
    async function main() {
        const body = await loadBody("https://example.com");
        console.log(body.length);
    }
    main();
');
```

Ειδικά για το HTTP, το ενσωματωμένο `fetch()` επιστρέφει ήδη πραγματικό promise JS και μπορεί να δρομολογηθεί μέσω του δικού σας πελάτη PHP με [προσαρμοσμένη μεταφορά fetch](https://phasis.inline0.dev/el/docs/interop/fetch-transport).

## Σημειώσεις απόδοσης

Κάθε κλήση συνάρτησης υποδοχής περνά μία φορά το όριο PHP↔JS για τα ορίσματα και μία για την τιμή επιστροφής. Και οι δύο μετατροπές δημιουργούν αντίγραφα, επομένως το κόστος τους είναι O(size of value) — οι πρωτογενείς τιμές έχουν σταθερό κόστος, ενώ οι πίνακες και τα αντικείμενα κοστίζουν ανάλογα με το μέγεθός τους.

Σε εσωτερικούς βρόχους υψηλής συχνότητας, προτιμήστε μία κλήση που κάνει περισσότερη δουλειά αντί για πολλές μικρές, και αποφύγετε να μεταφέρετε μεγάλους πίνακες όταν αλλάζουν μόνο λίγα πεδία.
