PHP 8.0: Πλήρης επισκόπηση των νέων χαρακτηριστικών (1/4)

πριν από 4 χρόνια Από David Grudl  

Κυκλοφόρησε η έκδοση 8.0 της PHP. Είναι τόσο γεμάτη με νέα χαρακτηριστικά όσο καμία προηγούμενη έκδοση. Η παρουσίασή τους απαιτεί τέσσερα ξεχωριστά άρθρα. Σε αυτό το πρώτο, θα δούμε τι νέο φέρνει από πλευράς γλώσσας.

Πριν βουτήξουμε στην PHP, να ξέρετε ότι η τρέχουσα έκδοση του Nette είναι πλήρως έτοιμη για την έκδοση 8. Επιπλέον, ως δώρο, κυκλοφόρησε και το Nette 2.4, το οποίο είναι πλήρως συμβατό μαζί της, οπότε από την πλευρά του framework, τίποτα δεν σας εμποδίζει να αρχίσετε να χρησιμοποιείτε τη νέα έκδοση.

Ονομαστικά ορίσματα

Και ξεκινάμε αμέσως με μια βόμβα, η οποία μπορεί άνετα να χαρακτηριστεί ως game changer. Πλέον, τα ορίσματα μπορούν να περαστούν σε συναρτήσεις και μεθόδους όχι μόνο με βάση τη θέση τους, αλλά και με βάση το όνομά τους. Αυτό είναι απολύτως φανταστικό στην περίπτωση που μια μέθοδος έχει πραγματικά πολλές παραμέτρους:

class Response implements IResponse
{
	public function setCookie(
		string $name,
		string $value,
		string|DateInterface|null $time,
		string $path = null,
		string $domain = null,
		bool $secure = null,
		bool $httpOnly = null,
		string $sameSite = null
	) {
		...
	}
}

Τα πρώτα δύο ορίσματα τα περνάμε με βάση τη θέση, τα υπόλοιπα με βάση το όνομα: (τα ονομαστικά πρέπει πάντα να ακολουθούν μετά τα ποζισιονικά)

$response->setCookie('lang', $lang, sameSite: 'None');

// αντί για το τρελό
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Πριν την έλευση αυτού του χαρακτηριστικού, υπήρχε σχέδιο να δημιουργηθεί στο Nette ένα νέο API για την αποστολή cookies, επειδή ο αριθμός των παραμέτρων της setCookie() είχε πραγματικά αυξηθεί και η ποζισιονική γραφή ήταν ασαφής. Τώρα αυτό δεν είναι πλέον απαραίτητο, επειδή τα ονομαστικά ορίσματα είναι σε αυτή την περίπτωση το πιο πρακτικό API. Το IDE θα τα προτείνει και έχουν έλεγχο τύπου.

Είναι επίσης εξαιρετικά χρήσιμα για την επεξήγηση λογικών παραμέτρων, όπου η χρήση τους μπορεί να μην είναι απαραίτητη, αλλά το ίδιο το true ή false δεν λέει πολλά:

// παλαιότερα
$db = $container->getService(Database::class, true);

// τώρα
$db = $container->getService(Database::class, need: true);

Τα ονόματα των παραμέτρων γίνονται πλέον μέρος του δημόσιου API. Δεν είναι δυνατό να αλλάξουν αυθαίρετα όπως μέχρι τώρα. Για το λόγο αυτό, και το Nette περνάει από έλεγχο για να διασφαλιστεί ότι όλες οι παράμετροι έχουν κατάλληλη ονομασία.

Τα ονομαστικά ορίσματα μπορούν να χρησιμοποιηθούν επίσης σε συνδυασμό με variadics:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// στο $args θα είναι ['b' => 2, 'c' => 3]

Πλέον, ο πίνακας $args μπορεί να περιέχει και μη αριθμητικά κλειδιά, κάτι που αποτελεί ένα ορισμένο BC break. Το ίδιο ισχύει και για τη συμπεριφορά της συνάρτησης call_user_func_array($func, $args), όπου τώρα τα κλειδιά στον πίνακα $args παίζουν σημαντικό ρόλο. Αντίθετα, οι συναρτήσεις της οικογένειας func_*() είναι απομονωμένες από τα ονομαστικά ορίσματα.

Με τα ονομαστικά ορίσματα συνδέεται στενά και το γεγονός ότι ο τελεστής splat ... μπορεί τώρα να αποσυμπιέσει και συσχετιστικούς πίνακες:

variadics(...['b' => 2, 'c' => 3]);

Παραδόξως, αυτό δεν λειτουργεί ακόμα μέσα σε πίνακες:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

Ο συνδυασμός ονομαστικών ορισμάτων και variadics δίνει τη δυνατότητα να έχουμε επιτέλους μια σταθερή σύνταξη, για παράδειγμα, για τη μέθοδο του presenter link(), στην οποία μπορούμε τώρα να περάσουμε ονομαστικά ορίσματα όπως και ποζισιονικά:

// παλαιότερα
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // έπρεπε να είναι πίνακας

// τώρα
$presenter->link('Product:detail', $id, page: 1);

Η σύνταξη για τα ονομαστικά ορίσματα είναι πολύ πιο σέξι από το να γράφουμε πίνακες, γι' αυτό και η Latte την υιοθέτησε αμέσως, όπου μπορεί να χρησιμοποιηθεί, για παράδειγμα, στις ετικέτες {include} και {link}:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Θα επιστρέψουμε στις ονομαστικές παραμέτρους στο τρίτο μέρος σε σχέση με τα attributes.

Η έκφραση μπορεί να προκαλέσει εξαίρεση

Η πρόκληση εξαίρεσης είναι τώρα μια έκφραση. Μπορείτε, για παράδειγμα, να την περικλείσετε σε παρενθέσεις και να την προσθέσετε σε μια συνθήκη if. Χμμμ, αυτό δεν ακούγεται πολύ πρακτικό. Αλλά αυτό είναι πιο ενδιαφέρον:

// παλαιότερα
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// τώρα, όταν το throw είναι έκφραση
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

Επειδή οι arrow functions μπορούσαν μέχρι τώρα να περιέχουν μόνο μία έκφραση, χάρη σε αυτό το χαρακτηριστικό μπορούν να προκαλούν εξαιρέσεις:

// μόνο μία έκφραση
$fn = fn() => throw new \Exception('oops');

Εκφράσεις Match

Η κατασκευή switch-case έχει δύο μεγάλα μειονεκτήματα:

  • χρησιμοποιεί μη αυστηρή σύγκριση == αντί για ===
  • πρέπει να προσέχετε να μην ξεχάσετε κατά λάθος το break

Η PHP λοιπόν έρχεται με μια εναλλακτική λύση με τη μορφή της νέας κατασκευής match, η οποία χρησιμοποιεί αυστηρή σύγκριση και αντίθετα δεν χρησιμοποιεί break.

Παράδειγμα κώδικα switch:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('ok');
        break;
    case 400:
        $message = $this->formatMessage('not found');
        break;
    case 500:
        $message = $this->formatMessage('server error');
        break;
    default:
        $message = 'unknown status code';
        break;
}

Και το ίδιο (μόνο με αυστηρή σύγκριση) γραμμένο με match:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('not found'),
    500 => $this->formatMessage('server error'),
    default => 'unknown status code',
};

Σημειώστε ότι το match δεν είναι δομή ελέγχου όπως το switch, αλλά μια έκφραση. Την τελική του τιμή στο παράδειγμα την αναθέτουμε σε μια μεταβλητή. Ταυτόχρονα, και οι μεμονωμένες “επιλογές” είναι εκφράσεις, οπότε δεν μπορείτε να γράψετε πολλαπλά βήματα, όπως στην περίπτωση του switch.

Εάν δεν υπάρξει αντιστοιχία με καμία από τις επιλογές (και δεν υπάρχει ρήτρα default), προκαλείται η εξαίρεση UnhandledMatchError.

Παρεμπιπτόντως, στο Latte υπάρχουν επίσης τα tags {switch}, {case} και {default}. Η λειτουργία τους αντιστοιχεί ακριβώς στο νέο match. Χρησιμοποιούν αυστηρή σύγκριση, δεν απαιτούν break και στο case είναι δυνατό να δηλωθούν πολλαπλές τιμές διαχωρισμένες με κόμματα.

Τελεστής Nullsafe

Η προαιρετική αλυσίδωση (optional chaining) επιτρέπει τη γραφή μιας έκφρασης, η αξιολόγηση της οποίας σταματά αν συναντήσει null. Και αυτό χάρη στον νέο τελεστή ?->. Αντικαθιστά πολύ κώδικα που διαφορετικά θα έλεγχε επανειλημμένα για null:

$user?->getAddress()?->street

// σημαίνει περίπου
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Γιατί “σημαίνει περίπου”; Επειδή στην πραγματικότητα η έκφραση αξιολογείται πιο έξυπνα και κανένα βήμα δεν επαναλαμβάνεται. Για παράδειγμα, το $user->getAddress() καλείται μόνο μία φορά, οπότε δεν μπορεί να προκύψει πρόβλημα που οφείλεται στο ότι η μέθοδος θα επέστρεφε κάτι διαφορετικό την πρώτη και τη δεύτερη φορά.

Αυτό το φρέσκο χαρακτηριστικό το έφερε πριν από ένα χρόνο στο Latte. Τώρα φτάνει και στην ίδια την PHP. Υπέροχα.

Προώθηση ιδιοτήτων κατασκευαστή

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

class Facade
{
	private Nette\Database\Connection $db;
	private Nette\Mail\Mailer $mailer;

	public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
	{
		$this->db = $db;
		$this->mailer = $mailer;
	}
}
class Facade
{
	public function __construct(
		private Nette\Database\Connection $db,
		private Nette\Mail\Mailer $mailer,
	) {}
}

Λειτουργεί με το Nette DI, μπορείτε να αρχίσετε να το χρησιμοποιείτε αμέσως.

Αυστηρή συμπεριφορά αριθμητικών και δυαδικών τελεστών

Αυτό που κάποτε εκτόξευσε τις δυναμικές γλώσσες scripting στην κορυφή, με τον καιρό έγινε το πιο αδύναμο σημείο τους. Η PHP κάποτε απαλλάχθηκε από τα “magic quotes”, την καταχώρηση καθολικών μεταβλητών και τώρα η χαλαρή συμπεριφορά αντικαθίσταται από την αυστηρότητα. Η εποχή που στην PHP μπορούσατε να προσθέτετε, να πολλαπλασιάζετε κ.λπ. σχεδόν οποιουσδήποτε τύπους δεδομένων, για τους οποίους αυτό δεν είχε κανένα απολύτως νόημα, έχει περάσει ανεπιστρεπτί. Ξεκινώντας από την έκδοση 7.0, η PHP γίνεται όλο και πιο αυστηρή και από την έκδοση 8.0, η προσπάθεια χρήσης οποιουδήποτε αριθμητικού/δυαδικού τελεστή σε πίνακες, αντικείμενα ή resources καταλήγει σε TypeError. Εξαίρεση αποτελεί η πρόσθεση πινάκων.

// αριθμητικοί και δυαδικοί τελεστές
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Πιο λογική σύγκριση συμβολοσειρών και αριθμών

Ή αλλιώς, κάντε τον χαλαρό τελεστή ξανά σπουδαίο.

Θα φαινόταν ότι για τον χαλαρό τελεστή == δεν υπάρχει πλέον θέση, ότι πρόκειται απλώς για τυπογραφικό λάθος κατά τη γραφή του ===, αλλά αυτή η αλλαγή τον επαναφέρει στον χάρτη. Αφού τον έχουμε, ας συμπεριφέρεται λογικά. Συνέπεια της προηγούμενης “παράλογης” σύγκρισης ήταν, για παράδειγμα, η συμπεριφορά της in_array(), η οποία μπορούσε να σας παγιδεύσει δυσάρεστα:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// εκπληκτικά επέστρεφε true
// από την PHP 8.0 επιστρέφει false

Η αλλαγή στη συμπεριφορά του == αφορά τη σύγκριση αριθμών και “αριθμητικών” συμβολοσειρών και φαίνεται στον παρακάτω πίνακα:

Σύγκριση Πριν PHP 8.0
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42 " true true
42 == "42foo" true false
42 == "abc42" false false
"42" == " 42" true true
"42" == "42 " false true

Είναι εκπληκτικό το γεγονός ότι πρόκειται για ένα BC break στον ίδιο τον πυρήνα της γλώσσας, το οποίο εγκρίθηκε χωρίς καμία αντίσταση. Και αυτό είναι καλό. Εδώ η JavaScript θα μπορούσε να ζηλέψει πολύ.

Αναφορά σφαλμάτων

Πολλές εσωτερικές συναρτήσεις προκαλούν τώρα TypeError και ValueError αντί για προειδοποιήσεις, οι οποίες μπορούσαν εύκολα να παραβλεφθούν. Έχει γίνει επαναταξινόμηση πολλών προειδοποιήσεων του πυρήνα. Ο τελεστής shutup @ δεν σιωπά πλέον τα μοιραία σφάλματα. Και το PDO στην προεπιλεγμένη λειτουργία προκαλεί εξαιρέσεις.

Αυτά τα πράγματα πάντα προσπαθούσε το Nette να τα λύσει με κάποιο τρόπο. Το Tracy τροποποιούσε τη συμπεριφορά του τελεστή shutup, το Database άλλαζε τη συμπεριφορά του PDO, το Utils περιέχει αντικαταστάσεις τυπικών συναρτήσεων που προκαλούν εξαιρέσεις αντί για διακριτικές προειδοποιήσεις κ.λπ. Είναι ωραίο να βλέπουμε ότι η αυστηρή κατεύθυνση, την οποία το Nette έχει στο DNA του, γίνεται η εγγενής κατεύθυνση της γλώσσας.

Πίνακες με αρνητικό δείκτη

$arr[-5] = 'first';
$arr[] = 'second';

Ποιο θα είναι το κλειδί του δεύτερου στοιχείου; Παλαιότερα ήταν 0, από την PHP 8 είναι -4.

Καταληκτικό κόμμα

Το τελευταίο μέρος όπου δεν μπορούσε να υπάρχει καταληκτικό κόμμα ήταν ο ορισμός των παραμέτρων της συνάρτησης. Αυτό ανήκει πλέον στο παρελθόν:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // καταληκτικό κόμμα
	) {
		....
	}

$object::class

Η μαγική σταθερά ::class λειτουργεί και με αντικείμενα $object::class, αντικαθιστώντας έτσι πλήρως τη συνάρτηση get_class().

catch χωρίς μεταβλητή

Και τέλος: στη ρήτρα catch δεν είναι απαραίτητο να δηλωθεί η μεταβλητή για την εξαίρεση:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // χωρίς $e
	$logger->log('....');
}

Στα επόμενα μέρη μας περιμένουν σημαντικές καινοτομίες στους τύπους δεδομένων, θα δείξουμε τι είναι τα attributes, ποιες νέες συναρτήσεις και κλάσεις εμφανίστηκαν στην PHP και θα παρουσιάσουμε τον Just in Time Compiler.

Πρόσφατες δημοσιεύσεις