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

3 χρόνια πριν Από το David Grudl  

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

Πριν εμβαθύνουμε στην PHP, να γνωρίζετε ότι η τρέχουσα έκδοση της Nette είναι πλήρως προετοιμασμένη για την όγδοη έκδοση. Επιπλέον, ως δώρο, έχει κυκλοφορήσει ένα πλήρως συμβατό 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');

// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

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

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

// before
$db = $container->getService(Database::class, true);

// now
$db = $container->getService(Database::class, need: true);

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

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

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

variadics(a: 1, b: 2, c: 3);
// $args will contain ['b' => 2, 'c' => 3]

Ο πίνακας $args μπορεί τώρα να περιέχει ακόμη και μη αριθμητικά κλειδιά, κάτι που αποτελεί ένα είδος διακοπής της BC. Το ίδιο ισχύει και για τη συμπεριφορά του 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 δίνει τη δυνατότητα να έχουμε τελικά μια σταθερή σύνταξη, για παράδειγμα, για τη μέθοδο link(), στην οποία μπορούμε να περάσουμε ονομαστικά ορίσματα καθώς και ορίσματα θέσης:

// before
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // had to be an array

// now
$presenter->link('Product:detail', $id, page: 1);

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

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

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

Μια έκφραση μπορεί να πετάξει μια εξαίρεση

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

// before
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// now, when throw is an expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

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

// only single expression
$fn = fn() => throw new \Exception('oops');

Έκφραση αγώνα

Η δήλωση 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.

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

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

Τελεστής Nullsafe

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

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

// approximately translates to
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

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

Αυτό το χαρακτηριστικό έφερε ο “Latte πριν από ένα χρόνο:https://blog.nette.org/…om-functions”. Τώρα το υιοθετεί και η ίδια η PHP. Υπέροχα.

Προώθηση της ιδιότητας του κατασκευαστή

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

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, μπορείτε να αρχίσετε να το χρησιμοποιείτε.

Αυστηρότεροι έλεγχοι τύπου για αριθμητικούς και bitwise τελεστές

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

// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Πιο υγιεινές συγκρίσεις συμβολοσειρών με αριθμούς

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

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

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

dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns 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 αντί για προειδοποιήσεις που ήταν εύκολο να αγνοηθούν. Ορισμένες προειδοποιήσεις του πυρήνα έχουν επαναταξινομηθεί. Ο τελεστής τερματισμού @ δεν αποσιωπά πλέον τα μοιραία σφάλματα. Και το PDO ρίχνει εξαιρέσεις από προεπιλογή.

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

Αρνητικές αυξήσεις κλειδιών συστοιχιών

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

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

Παραλειπόμενο κόμμα

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

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // trailing comma
	) {
		....
	}

$object::class

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

Σύλληψη εξαιρέσεων μόνο με βάση τον τύπο

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

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

} catch (MissingServiceException) {  // no $e
	$logger->log('....');
}

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

Τελευταίες θέσεις