PHP 8.0: Νέες συναρτήσεις, κλάσεις και JIT (4/4)
Κυκλοφόρησε η έκδοση 8.0 της PHP. Είναι τόσο γεμάτη με νέα χαρακτηριστικά όσο καμία προηγούμενη έκδοση. Η παρουσίασή τους απαιτεί τέσσερα ξεχωριστά άρθρα. Σε αυτό το τελευταίο, θα εξετάσουμε τις νέες συναρτήσεις και κλάσεις και θα παρουσιάσουμε τον Just in Time Compiler.

Νέες συναρτήσεις
Η τυπική βιβλιοθήκη της PHP διαθέτει εκατοντάδες συναρτήσεις και στην έκδοση 8.0 εμφανίστηκαν έξι νέες. Φαίνεται λίγο, αλλά οι περισσότερες από αυτές καλύπτουν αδύνατα σημεία της γλώσσας. Κάτι που ταιριάζει όμορφα με το πνεύμα ολόκληρης της έκδοσης 8.0, η οποία ολοκληρώνει και ενοποιεί την PHP όπως καμία προηγούμενη έκδοση. Μια επισκόπηση όλων των νέων συναρτήσεων και μεθόδων μπορείτε να βρείτε στον οδηγό μετεγκατάστασης.
str_contains()
str_starts_with()
str_ends_with()
Συναρτήσεις για τον έλεγχο αν μια συμβολοσειρά αρχίζει, τελειώνει ή περιέχει μια υποσυμβολοσειρά.
if (str_contains('Nette', 'te')) {
...
}
Μαζί με την έλευση αυτής της τριάδας, η PHP ορίζει πώς να χειρίζεται την κενή συμβολοσειρά κατά την αναζήτηση, σύμφωνα με το οποίο καθοδηγούνται και όλες οι άλλες σχετικές συναρτήσεις, και συγκεκριμένα ότι η κενή συμβολοσειρά βρίσκεται παντού:
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (παλαιότερα false)
Χάρη σε αυτό, η συμπεριφορά της τριάδας είναι εντελώς πανομοιότυπη με τα αντίστοιχα στο Nette:
str_contains() # Nette\Utils\String::contains()
str_starts_with() # Nette\Utils\String::startsWith()
str_ends_with() # Nette\Utils\String::endsWith()
Γιατί είναι τόσο σημαντικές αυτές οι
συναρτήσεις; Οι τυπικές βιβλιοθήκες όλων
των γλωσσών είναι πάντα φορτωμένες με
ιστορική εξέλιξη και δεν μπορεί να
αποφευχθεί η εμφάνιση ασυνεπειών και λαθών.
Αλλά ταυτόχρονα, αποτελούν την ταυτότητα
της κάθε γλώσσας. Είναι περίεργο όταν σε μια
PHP 25 ετών λείπουν συναρτήσεις για τόσο
βασικές λειτουργίες, όπως η επιστροφή του
πρώτου ή του τελευταίου στοιχείου από έναν
πίνακα, η διαφυγή HTML χωρίς παγίδες
(htmlspecialchars
δεν διαφεύγει τον
απόστροφο), ή ακριβώς η αναζήτηση
συμβολοσειράς σε συμβολοσειρά. Το ότι
μπορεί να παρακαμφθεί κάπως δεν
ευσταθεί, επειδή το αποτέλεσμα τότε δεν
είναι ευανάγνωστος και κατανοητός κώδικας.
Είναι ένα μάθημα για όλους τους δημιουργούς
API. Όταν βλέπετε ότι ένα σημαντικό μέρος της
τεκμηρίωσης μιας συνάρτησης
καταλαμβάνεται από την εξήγηση των παγίδων
(όπως οι τιμές επιστροφής της strpos
),
είναι ένα σαφές σήμα για την τροποποίηση
της βιβλιοθήκης και την προσθήκη ακριβώς
της str_contains
.
get_debug_type()
Αντικαθιστά το ήδη παρωχημένο
get_type()
. Αντί για μακροσκελείς τύπους
όπως integer
, επιστρέφει το σημερινό
int
, στην περίπτωση αντικειμένων
επιστρέφει απευθείας τον τύπο:
Τιμή | gettype() | get_debug_type() |
---|---|---|
'abc' |
string |
string |
[1, 2] |
array |
array |
231 |
integer |
int |
3.14 |
double |
float |
true |
boolean |
bool |
null |
NULL |
null |
new stdClass |
object |
stdClass |
new Foo\Bar |
object |
Foo\Bar |
function() {} |
object |
Closure |
new class {} |
object |
class@anonymous |
new class extends Foo {} |
object |
Foo@anonymous |
curl_init() |
resource |
resource (curl) |
curl_close($ch) |
resource (closed) |
resource (closed) |
Αντικειμενοποίηση πόρων
Οι τιμές τύπου resource προέρχονται από την εποχή που η PHP δεν είχε ακόμη αντικείμενα, αλλά στην πραγματικότητα τα χρειαζόταν. Έτσι ήρθαν στον κόσμο οι resources. Σήμερα έχουμε αντικείμενα και σε σύγκριση με τους resources λειτουργούν πολύ καλύτερα με τον garbage collector, οπότε το σχέδιο είναι σταδιακά να αντικατασταθούν όλοι από αντικείμενα.
Από την PHP 8.0, μετατρέπονται σε αντικείμενα οι resources εικόνων, συνδέσεων curl, openssl, xml, κ.λπ.. Στην PHP 8.1 θα έρθει η σειρά των συνδέσεων FTP κ.λπ.
$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage // true
is_resource($res) // false - BC break
Αυτά τα αντικείμενα δεν έχουν προς το
παρόν καμία μέθοδο, ούτε μπορείτε να
δημιουργήσετε απευθείας τις περιπτώσεις
τους. Προς το παρόν, πρόκειται πραγματικά
μόνο για την αφαίρεση των παρωχημένων resources
από την PHP χωρίς αλλαγή του API. Και αυτό είναι
καλό, επειδή η δημιουργία ενός καλού API
είναι ένα ξεχωριστό και απαιτητικό έργο.
Κανείς δεν επιθυμεί να δημιουργηθούν στην
PHP άλλες κλάσεις όπως η SplFileObject
με
μεθόδους που ονομάζονται fgetc()
ή
fgets()
.
PhpToken
Σε αντικείμενα μεταφέρεται και ο tokenizér
και συνεπώς οι συναρτήσεις γύρω από το
token_get_all
. Αυτή τη φορά δεν πρόκειται
για απαλλαγή από resources, αλλά παίρνουμε ένα
πλήρες αντικείμενο που αντιπροσωπεύει ένα
PHP token.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // περίπτωση PhpToken
echo $token->id; // T_OPEN_TAG
echo $token->text; // '<?php'
echo $token->line; // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING); // false
echo $token->isIgnorable(); // true
Η μέθοδος isIgnorable()
επιστρέφει true για
τα tokens T_WHITESPACE
, T_COMMENT
,
T_DOC_COMMENT
και T_OPEN_TAG
.
Weak maps
Οι Weak maps σχετίζονται με τον gargabe collector, ο οποίος απελευθερώνει από τη μνήμη όλα τα αντικείμενα και τις τιμές που δεν χρησιμοποιούνται πλέον (δηλαδή δεν υπάρχει καμία χρησιμοποιούμενη μεταβλητή ή property που να τα περιέχει). Δεδομένου ότι η ζωή ενός PHP thread είναι εφήμερη και η μνήμη που έχουμε σήμερα στους servers είναι άφθονη, τα ζητήματα γύρω από την αποτελεσματική απελευθέρωση μνήμης συνήθως δεν μας απασχολούν καθόλου. Αλλά για scripts που τρέχουν για μεγαλύτερο χρονικό διάστημα, είναι θεμελιώδη.
Το αντικείμενο WeakMap
είναι παρόμοιο
με το SplObjectStorage
. Και στα δύο, ως
κλειδιά χρησιμοποιούνται αντικείμενα και
επιτρέπουν την αποθήκευση οποιωνδήποτε
τιμών κάτω από αυτά. Η διαφορά είναι ότι το
WeakMap
δεν εμποδίζει την απελευθέρωση
του αντικειμένου από τον garbage collector. Δηλαδή,
αν το μοναδικό μέρος όπου εξακολουθεί να
υπάρχει το αντικείμενο είναι το κλειδί στο
weak map, θα αφαιρεθεί από το map και τη μνήμη.
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 'δεδομένα για το $obj';
dump(count($map)); // 1
unset($obj);
dump(count($map)); // 0
Σε τι χρησιμεύει αυτό; Για παράδειγμα, για
caching. Ας έχουμε μια μέθοδο loadComments()
,
στην οποία περνάμε ένα άρθρο blog και αυτή
επιστρέφει όλα τα σχόλιά του. Επειδή η
μέθοδος καλείται επανειλημμένα με το ίδιο
άρθρο, θα δημιουργήσουμε επίσης μια
getComments()
, η οποία θα αποθηκεύει
προσωρινά το αποτέλεσμα της πρώτης
μεθόδου:
class Comments
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap;
}
public function getComments(Article $article): ?array
{
$this->cache[$article] ??= $this->loadComments($article);
return $this->cache[$article]
}
...
}
Το κόλπο είναι ότι τη στιγμή που
απελευθερώνεται το αντικείμενο $article
(ίσως η εφαρμογή αρχίσει να δουλεύει με άλλο
άρθρο), απελευθερώνεται και η καταχώρησή
του από την cache.
PHP JIT (Just in Time Compiler)
Ίσως γνωρίζετε ότι η PHP μεταγλωττίζεται στο λεγόμενο opcode, το οποίο είναι low-level εντολές, τις οποίες μπορείτε να δείτε για παράδειγμα εδώ και τις οποίες εκτελεί η εικονική μηχανή της PHP. Και τι είναι το JIT; Το JIT μπορεί να μεταγλωττίζει διαφανώς την PHP απευθείας σε κώδικα μηχανής, τον οποίο εκτελεί απευθείας ο επεξεργαστής, παρακάμπτοντας έτσι την πιο αργή εκτέλεση από την εικονική μηχανή.
Το JIT λοιπόν έχει σκοπό να επιταχύνει την PHP.
Η προσπάθεια υλοποίησης του JIT στην PHP χρονολογείται από το 2011 και πίσω από αυτήν βρίσκεται ο Dmitry Stogov. Από τότε, έχει δοκιμάσει 3 διαφορετικές υλοποιήσεις, αλλά καμία από αυτές δεν έφτασε στην επίσημη PHP για τους εξής λόγους: το αποτέλεσμα ποτέ δεν ήταν κάποια ουσιαστική αύξηση της απόδοσης για τυπικές web εφαρμογές· περιπλέκει τη συντήρηση της PHP (δηλαδή κανείς εκτός από τον Dmitry δεν το καταλαβαίνει 😉)· υπήρχαν άλλοι τρόποι βελτίωσης της απόδοσης χωρίς να χρειάζεται η χρήση JIT.
Η αλματώδης αύξηση της απόδοσης της PHP στην έκδοση 7 ήταν ένα παράπλευρο προϊόν της δουλειάς πάνω στο JIT, αν και παραδόξως δεν υλοποιήθηκε. Αυτό συμβαίνει τώρα στην PHP 8. Αλλά αμέσως θα μετριάσω τις υπερβολικές προσδοκίες: πιθανότατα δεν θα παρατηρήσετε καμία επιτάχυνση.
Γιατί λοιπόν μπαίνει το JIT στην PHP; Πρώτον, οι άλλοι δρόμοι για τη βελτίωση της απόδοσης σιγά σιγά εξαντλούνται και το JIT είναι απλά στη σειρά. Στις συνηθισμένες web εφαρμογές μπορεί να μην φέρνει επιτάχυνση, αλλά επιταχύνει σημαντικά, για παράδειγμα, τους μαθηματικούς υπολογισμούς. Ανοίγει έτσι η δυνατότητα να αρχίσουμε να γράφουμε αυτά τα πράγματα στην PHP. Και στην πραγματικότητα, θα ήταν δυνατό να υλοποιηθούν απευθείας στην PHP συναρτήσεις που μέχρι τώρα απαιτούσαν υλοποίηση απευθείας σε C λόγω ταχύτητας.
Το JIT είναι μέρος της επέκτασης opcache
και ενεργοποιείται μαζί με αυτήν στο php.ini
(διαβάστε την τεκμηρίωση
για τους τέσσερις αυτούς αριθμούς):
zend_extension=php_opcache.dll
opcache.jit=1205 ; διαμόρφωση με τη χρήση της τετράδας OTRC
opcache.enable_cli=1 ; για να λειτουργεί και στο CLI
opcache.jit_buffer_size=128M ; δεσμευμένη μνήμη για τον μεταγλωττισμένο κώδικα
Ότι το JIT τρέχει θα το μάθετε, για παράδειγμα, στον πίνακα πληροφοριών στο Tracy Bar.
Το JIT λειτουργεί πολύ καλά όταν όλες οι
μεταβλητές έχουν σαφώς καθορισμένους
τύπους και δεν μπορούν να αλλάξουν κατά την
επαναλαμβανόμενη κλήση του ίδιου κώδικα.
Είμαι λοιπόν περίεργος αν κάποτε θα
δηλώνουμε στην PHP τύπους και για τις
μεταβλητές: string $s = 'Γεια, αυτό είναι το τέλος της σειράς';
Για να υποβάλετε ένα σχόλιο, συνδεθείτε