PHP 8.0: Νέα στους τύπους δεδομένων (2/4)

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

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

Ας πάμε πίσω στην ιστορία. Η εισαγωγή των υποδείξεων κλιμακωτών τύπων ήταν μια σημαντική καινοτομία στην PHP 7. Παραλίγο να μην συμβεί. Ο Andreu Faulds, ο συγγραφέας της έξυπνης λύσης, η οποία ήταν πλήρως συμβατή προς τα πίσω και προαιρετική χάρη στο declare(strict_types=1), απορρίφθηκε σκληρά από την κοινότητα. Ευτυχώς, ο Anthony Ferrara υπερασπίστηκε εκείνη την εποχή την ίδια και την πρόταση, ξεκίνησε μια εκστρατεία και το RFC πέρασε πολύ κοντά. Ουφ. Οι περισσότερες από τις ειδήσεις στην PHP 8 είναι ευγενική προσφορά του θρυλικού Nikita Popov και όλες πέρασαν την ψηφοφορία σαν το μαχαίρι στο βούτυρο. Ο κόσμος αλλάζει προς το καλύτερο.

Η PHP 8 φέρνει τους τύπους στην τελειότητα. Η συντριπτική πλειοψηφία των σχολίων phpDoc όπως @param, @return και @var θα αντικατασταθεί από εγγενή συμβολισμό. Αλλά το πιο σημαντικό είναι ότι οι τύποι θα ελέγχονται από τη μηχανή της PHP. Μόνο οι περιγραφές δομών όπως το string[] ή οι πιο σύνθετες επισημάνσεις για το PHPStan θα παραμείνουν στα σχόλια.

Ενωσιακοί τύποι

Οι ενωτικοί τύποι είναι μια απαρίθμηση δύο ή περισσότερων τύπων που μπορεί να δεχτεί μια μεταβλητή:

class Button
{
	private string|object $caption;

	public function setCaption(string|object $caption)
	{
		$this->caption = $caption;
	}
}

Ορισμένοι τύποι ένωσης έχουν εισαχθεί στην PHP και στο παρελθόν. Μηδενιζόμενοι τύποι, για παράδειγμα. Όπως το ?string, το οποίο είναι ισοδύναμο με τον τύπο ένωσης string|null. Ο συμβολισμός με ερωτηματικά μπορεί να θεωρηθεί συντομογραφία. Φυσικά, λειτουργεί και στην PHP 8, αλλά δεν μπορείτε να τη συνδυάσετε με κάθετες γραμμές. Έτσι, αντί για ?string|object πρέπει να γράψετε string|object|null. Επιπλέον, το iterable ήταν πάντα ισοδύναμο με το array|Traversable. Ίσως εκπλαγείτε από το γεγονός ότι το float είναι επίσης ένας τύπος ένωσης, καθώς δέχεται και το int|float, αλλά ρίχνει την τιμή στο float.

Δεν μπορείτε να χρησιμοποιήσετε τους ψευδότυπους void και mixed σε ενώσεις, διότι αυτό δεν θα είχε νόημα.

Η Nette είναι έτοιμη για τύπους ένωσης. Στο Schema, το Expect::from() τους δέχεται, και οι παρουσιαστές τους δέχονται επίσης. Μπορείτε να τους χρησιμοποιήσετε σε μεθόδους render και action, για παράδειγμα:

public function renderDetail(int|array $id)
{
	...
}

Από την άλλη πλευρά, η αυτόματη καλωδίωση στο Nette DI απορρίπτει τους ενωτικούς τύπους. Μέχρι στιγμής, δεν υπάρχει καμία περίπτωση χρήσης όπου θα είχε νόημα ο κατασκευαστής να δέχεται είτε το ένα είτε το άλλο αντικείμενο. Φυσικά, αν εμφανιστεί μια τέτοια περίπτωση χρήσης, θα είναι δυνατόν να προσαρμοστεί ανάλογα η συμπεριφορά του δοχείου.

Οι μέθοδοι getParameterType(), getReturnType() και getPropertyType() στο Nette\Utils\Reflection ρίχνουν μια εξαίρεση στην περίπτωση του τύπου union (δηλαδή στην έκδοση 3.1– στην προηγούμενη έκδοση 3.0, αυτές οι μέθοδοι επιστρέφουν για να διατηρήσουν τη συμβατότητα με null).

mixed

Ο ψευδότυπος mixed δέχεται οποιαδήποτε τιμή.

Στην περίπτωση των παραμέτρων και των ιδιοτήτων, έχει ως αποτέλεσμα την ίδια συμπεριφορά με αυτή που θα είχαμε αν δεν προσδιορίζαμε κανέναν τύπο. Ποιος είναι λοιπόν ο σκοπός του; Να διακρίνει πότε ένας τύπος απλώς λείπει και πότε είναι σκόπιμα mixed.

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

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

function dump(mixed $var): mixed
{
	// print variable
	return $var;
}

false

Σε αντίθεση με το mixed, μπορείτε να χρησιμοποιήσετε το νέο ψευδότυπο false αποκλειστικά σε τύπους ένωσης. Προέκυψε από την ανάγκη περιγραφής του τύπου επιστροφής των εγγενών συναρτήσεων, οι οποίες ιστορικά επιστρέφουν false σε περίπτωση αποτυχίας:

function strpos(string $haystack, string $needle): int|false
{
}

Ως εκ τούτου, δεν υπάρχει τύπος true. Ούτε μπορείτε να χρησιμοποιήσετε μόνο το false, ούτε το false|null, ούτε το bool|false.

static

Ο ψευδότυπος static μπορεί να χρησιμοποιηθεί μόνο ως τύπος επιστροφής μιας μεθόδου. Λέει ότι η μέθοδος επιστρέφει ένα αντικείμενο του ίδιου τύπου με το ίδιο το αντικείμενο (ενώ το self λέει ότι επιστρέφει την κλάση στην οποία ορίζεται η μέθοδος). Είναι εξαιρετικός για την περιγραφή ρευστών διεπαφών:

class Item
{
	public function setValue($val): static
	{
		$this->value = $val;
		return $this;
	}
}

class ItemChild extends Item
{
	public function childMethod()
	{
	}
}

$child = new ItemChild;
$child->setValue(10)
	->childMethod();

resource

Αυτός ο τύπος δεν υπάρχει στην PHP 8 και δεν θα εισαχθεί στο μέλλον. Οι πόροι είναι ένα κατάλοιπο από την εποχή που η PHP δεν είχε αντικείμενα. Σταδιακά, οι πόροι πρόκειται να αντικατασταθούν από αντικείμενα. Τελικά, θα εξαφανιστούν εντελώς. Για παράδειγμα, η PHP 8.0 αντικαθιστά τον πόρο image με το αντικείμενο GdImage και τον πόρο curl με το αντικείμενο CurlHandle.

Stringable

Είναι μια διεπαφή που υλοποιείται αυτόματα από κάθε αντικείμενο με μια μαγική μέθοδο __toString().

class Email
{
	public function __toString(): string
	{
		return $this->value;
	}
}

function print(Stringable|string $s)
{
}

print('abc');
print(new Email);

Είναι δυνατόν να δηλωθεί ρητά το class Email implements Stringable στον ορισμό της κλάσης, αλλά δεν είναι απαραίτητο.

Nette\Utils\Html αντικατοπτρίζει επίσης αυτό το σχήμα ονοματοδοσίας υλοποιώντας τη διεπαφή Nette\HtmlStringable αντί της προηγούμενης IHtmlString. Αντικείμενα αυτού του τύπου, για παράδειγμα, δεν διαφεύγουν από το Latte.

Τύπος διακύμανσης, συνδιακύμανσης, συνδιακύμανσης

Η αρχή υποκατάστασης Liskov (LSP) δηλώνει ότι οι κλάσεις επέκτασης και οι υλοποιήσεις διεπαφών δεν πρέπει ποτέ να απαιτούν περισσότερα και να παρέχουν λιγότερα από τον γονέα. Δηλαδή, η μέθοδος-παιδί δεν πρέπει να απαιτεί περισσότερα ορίσματα ή να δέχεται στενότερο εύρος τύπων για παραμέτρους από τη γονέα και αντίστροφα, δεν πρέπει να επιστρέφει μεγαλύτερο εύρος τύπων. Μπορεί όμως να επιστρέφει λιγότερους. Γιατί; Επειδή διαφορετικά, η κληρονομικότητα θα έσπαγε. Μια συνάρτηση θα δεχόταν ένα αντικείμενο συγκεκριμένου τύπου, αλλά δεν θα είχε ιδέα ποιες παραμέτρους μπορεί να περάσει στις μεθόδους της και ποιους τύπους θα επέστρεφαν. Οποιοδήποτε παιδί θα μπορούσε να τη σπάσει.

Έτσι, στην OOP, η κλάση παιδί μπορεί:

  • να δέχεται ένα ευρύτερο εύρος τύπων στις παραμέτρους (αυτό ονομάζεται αντιπαραβολή)
  • να επιστρέφει ένα στενότερο εύρος τύπων (συνδιακύμανση)
  • και οι ιδιότητες δεν μπορούν να αλλάξουν τύπο (είναι αναλλοίωτες)

Η PHP είναι σε θέση να το κάνει αυτό από την έκδοση 7.4 και όλοι οι νεοεισαχθέντες τύποι στην PHP 8.0 υποστηρίζουν επίσης contravariance και covariance.

Στην περίπτωση του mixed, το παιδί μπορεί να περιορίσει την τιμή επιστροφής σε οποιοδήποτε τύπο, αλλά όχι το void, καθώς δεν αναπαριστά μια τιμή, αλλά μάλλον την απουσία της. Το παιδί δεν μπορεί επίσης να παραλείψει μια δήλωση τύπου, καθώς αυτό επιτρέπει επίσης την απουσία τιμής.

class A
{
    public function foo(mixed $foo): mixed
    {}
}

class B extends A
{
    public function foo($foo): string
    {}
}

Οι ενωσιακοί τύποι μπορούν επίσης να επεκταθούν στις παραμέτρους και να περιοριστούν στις τιμές επιστροφής:

class A
{
    public function foo(string|int $foo): string|int
    {}
}

class B extends A
{
    public function foo(string|int|float $foo): string
    {}
}

Επιπλέον, το false μπορεί να επεκταθεί στο bool στην παράμετρο ή αντίστροφα το bool στο false στην τιμή επιστροφής.

Όλες οι παραβάσεις κατά της συνδιακύμανσης/συνδιακύμανσης οδηγούν σε μοιραίο σφάλμα στην PHP 8.0.

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

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