PHP 8.0: Χαρακτηριστικά (3/4)

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

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

Τα attributes παρέχουν έναν εντελώς νέο τρόπο να γράφετε δομημένα μεταδεδομένα για κλάσεις και όλα τα μέλη της, καθώς και για συναρτήσεις, closures και τις παραμέτρους τους. Τα σχόλια της PhpDoc χρησιμοποιούνταν μέχρι τώρα για αυτό το σκοπό, αλλά η σύνταξή τους ήταν πάντα τόσο χαλαρή και ασυνεπής που δεν ήταν δυνατόν να ξεκινήσει η μηχανική επεξεργασία τους. Ως εκ τούτου, αντικαθίστανται από χαρακτηριστικά με καθορισμένη σύνταξη και υποστήριξη σε κλάσεις αντανάκλασης.

Εξαιτίας αυτού, οι βιβλιοθήκες που ανέκτησαν προηγουμένως μεταδεδομένα αναλύοντας τα σχόλια phpDoc θα μπορούν να τα αντικαταστήσουν με χαρακτηριστικά. Ένα παράδειγμα είναι η Nette, όπου στις τελευταίες εκδόσεις των Application και DI μπορείτε ήδη να χρησιμοποιήσετε τα attributes Persistent, CrossOrigin και Inject αντί για τα annotations @persistent, @crossOrigin και @inject.

Κώδικας που χρησιμοποιεί σημειώσεις:

/**
 * @persistent(comp1, comp2)
 */
class SomePresenter
{
	/** @persistent */
	public $page = 1;

	/** @inject */
	public Facade $facade;

	/**
	 * @crossOrigin
	 */
	public function handleSomething()
	{
	}
}

Το ίδιο με τα χαρακτηριστικά:

use Nette\Application\Attributes\CrossOrigin;
use Nette\Application\Attributes\Persistent;
use Nette\DI\Attributes\Inject;

#[Persistent('comp1', 'comp2')]
class SomePresenter
{
	#[Persistent]
	public int $page = 1;

	#[Inject]
	public Facade $facade;

	#[CrossOrigin]
	public function handleSomething()
	{
	}
}

Η PHP αξιολογεί τα ονόματα των χαρακτηριστικών με τον ίδιο τρόπο σαν να επρόκειτο για κλάσεις, στο πλαίσιο των χώρων ονομάτων και των ρητρών use. Έτσι, θα ήταν ακόμη και δυνατό να τα γράψετε, για παράδειγμα, ως εξής:

use Nette\Application\Attributes;

class SomePresenter
{
	#[Attributes\Persistent]
	public int $page = 1;

	#[\Nette\DI\Attributes\Inject]
	public Facade $facade;

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

Σύνταξη

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

Η σύνταξη ενός μεμονωμένου χαρακτηριστικού μοιάζει με τη δημιουργία μιας περίπτωσης αντικειμένου αν παραλείψουμε τον τελεστή new. Έτσι, το όνομα της κλάσης ακολουθούμενο από προαιρετικά ορίσματα μέσα σε παρενθέσεις:

#[Column('string', 32, true, false)]#
protected $username;

Και εδώ είναι το σημείο όπου μπορεί να χρησιμοποιηθεί το νέο καυτό χαρακτηριστικό της PHP 8.0 – τα ονομαστικά ορίσματα:

#[Column(
	type: 'string',
	length: 32,
	unique: true,
	nullable: false,
)]#
protected $username;

Κάθε στοιχείο μπορεί να έχει πολλαπλά χαρακτηριστικά που μπορούν να γραφτούν μεμονωμένα ή να διαχωριστούν με κόμμα:

#[Inject]
#[Lazy]
public Foo $foo;

#[Inject, Lazy]
public Bar $bar;

Το ακόλουθο χαρακτηριστικό ισχύει και για τις τρεις ιδιότητες:

#[Common]
private $a, $b, $c;

μπορούν να χρησιμοποιηθούν ως ορίσματα σε ιδιότητες:

#[
	ScalarExpression(1 + 1),
	ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
	BitShift(4 >> 1, 4 << 1),
	BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]

Δυστυχώς, η τιμή ενός ορίσματος δεν μπορεί να είναι ένα άλλο χαρακτηριστικό, δηλαδή τα χαρακτηριστικά δεν μπορούν να φωλιαστούν. Για παράδειγμα, δεν υπάρχει ένας απλός τρόπος για να μετατραπεί η ακόλουθη σημείωση που χρησιμοποιείται στο Doctrine σε χαρακτηριστικά:

/**
 * @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
 */

Επίσης, δεν υπάρχει αντίστοιχο χαρακτηριστικό για το αρχείο phpDoc, δηλαδή ένα σχόλιο που βρίσκεται στην αρχή ενός αρχείου, το οποίο χρησιμοποιείται, για παράδειγμα, από το Nette Tester.

Αντανάκλαση του χαρακτηριστικού

Το ποια χαρακτηριστικά έχουν τα επιμέρους στοιχεία μπορεί να προσδιοριστεί με τη χρήση της αντανάκλασης. Οι κλάσεις αντανάκλασης έχουν μια νέα μέθοδο getAttributes(), η οποία επιστρέφει έναν πίνακα αντικειμένων ReflectionAttribute.

use MyAttributes\Example;

#[Example('Hello', 123)]
class Foo
{}

$reflection = new ReflectionClass(Foo::class);

foreach ($reflection->getAttributes() as $attribute) {
	$attribute->getName();      // full attribute name, e.g. MyAttributes\Example
	$attribute->getArguments(); // ['Hello', 123]
	$attribute->newInstance();  // returns an instance: new MyAttributes\Example('Hello', 123)
}

Τα επιστρεφόμενα χαρακτηριστικά μπορούν να φιλτραριστούν μέσω μιας παραμέτρου, π.χ. η $reflection->getAttributes(Example::class) επιστρέφει μόνο τα χαρακτηριστικά Example.

Κατηγορίες χαρακτηριστικών

Η κλάση χαρακτηριστικών MyAttributes\Example μπορεί να μην υπάρχει. Μόνο μια κλήση μεθόδου newInstance() απαιτεί την ύπαρξή της επειδή την ενσαρκώνει. Ας τη γράψουμε λοιπόν. Θα είναι μια εντελώς συνηθισμένη κλάση, μόνο που πρέπει να παρέχουμε το χαρακτηριστικό Attribute (δηλαδή από το παγκόσμιο χώρο ονομάτων του συστήματος):

namespace MyAttributes;

use Attribute;

#[Attribute]
class Example
{
	public function __construct(string $message, int $number)
	{
		...
	}
}

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

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
	...
}

Οι σημαίες TARGET_CLASS, TARGET_FUNCTION, TARGET_METHOD, TARGET_PROPERTY, TARGET_CLASS_CONSTANT, TARGET_PARAMETER και η προεπιλεγμένη TARGET_ALL είναι διαθέσιμες.

Αλλά προσέξτε, η επαλήθευση της σωστής ή λανθασμένης χρήσης γίνεται εκπληκτικά μόνο όταν καλείται η μέθοδος newInstance(). Ο ίδιος ο μεταγλωττιστής δεν πραγματοποιεί αυτόν τον έλεγχο.

Το μέλλον με τα χαρακτηριστικά

Χάρη στα χαρακτηριστικά και τους νέους τύπους, τα σχόλια τεκμηρίωσης της PHP για πρώτη φορά στην ιστορία τους θα γίνουν πραγματικά μόνο σχόλια τεκμηρίωσης. Το PhpStorm έρχεται ήδη με προσαρμοσμένα χαρακτηριστικά, τα οποία μπορούν να αντικαταστήσουν, για παράδειγμα, το annotation @deprecated. Και μπορεί να υποτεθεί ότι αυτό το χαρακτηριστικό θα είναι μια μέρα στην PHP από προεπιλογή. Ομοίως, θα αντικατασταθούν και άλλες επισημάνσεις, όπως το @throws κ.λπ.

Παρόλο που η Nette χρησιμοποιούσε annotations από την πρώτη κιόλας έκδοσή της για να υποδεικνύει μόνιμες παραμέτρους και συστατικά, δεν χρησιμοποιήθηκαν μαζικότερα, επειδή δεν ήταν μια εγγενής κατασκευή της γλώσσας, οπότε οι συντάκτες δεν τις πρότειναν και ήταν εύκολο να γίνει κάποιο λάθος. Παρόλο που αυτό έχει ήδη αντιμετωπιστεί από πρόσθετα επεξεργαστών, ο πραγματικά εγγενής τρόπος, που φέρνουν τα attributes, ανοίγει εντελώς νέες δυνατότητες.

Παρεμπιπτόντως, τα attributes έχουν αποκτήσει μια εξαίρεση στο πρότυπο κωδικοποίησης Nette, το οποίο απαιτεί το όνομα της κλάσης, εκτός από εξειδίκευση (π.χ. Product, InvalidValue), να περιέχει και μια γενικότητα (π.χ. ProductPresenter, InvalidValueException). Διαφορετικά, όταν χρησιμοποιείται στον κώδικα, δεν θα ήταν σαφές τι ακριβώς αντιπροσωπεύει η κλάση. Για τα χαρακτηριστικά, αυτό δεν είναι επιθυμητό, οπότε η κλάση ονομάζεται Inject αντί για InjectAttribute.

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

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