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

Τα attributes φέρνουν έναν εντελώς νέο τρόπο για τη γραφή δομημένων μεταδεδομένων σε κλάσεις και όλα τα μέλη τους, καθώς και σε συναρτήσεις, closures και τις παραμέτρους τους. Για το σκοπό αυτό, μέχρι τώρα χρησιμοποιούνταν τα phpDoc σχόλια, αλλά η σύνταξή τους ήταν πάντα τόσο ελεύθερη και ανομοιόμορφη που δεν ήταν δυνατό να αρχίσουν να επεξεργάζονται μηχανικά. Γι' αυτό αντικαθίστανται από τα attributes με σταθερή σύνταξη και υποστήριξη στις κλάσεις reflection.
Έτσι, οι βιβλιοθήκες που μέχρι τώρα
αποκτούσαν μεταδεδομένα αναλύοντας τα phpDoc
σχόλια, θα μπορούν να τα αντικαταστήσουν με
attributes. Παράδειγμα είναι το Nette, όπου στις
τελευταίες εκδόσεις των Application και DI
μπορείτε ήδη να χρησιμοποιείτε τα attributes
Persistent
, CrossOrigin
και Inject
αντί για τις annotations @persistent
,
@crossOrigin
και @inject
.
Κώδικας που χρησιμοποιεί annotations:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Το ίδιο με attributes:
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 αξιολογεί τα ονόματα των attributes με τον
ίδιο τρόπο σαν να ήταν κλάσεις, δηλαδή στο
πλαίσιο του namespace και των ρητρών use
.
Έτσι, θα μπορούσαν να γραφτούν, για
παράδειγμα, και ως εξής:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
Η κλάση που αντιπροσωπεύει ένα attribute μπορεί να υπάρχει ή και να μην υπάρχει. Είναι όμως σίγουρα καλύτερο να υπάρχει, γιατί τότε ο editor μπορεί να την προτείνει κατά τη γραφή, ο στατικός αναλυτής αναγνωρίζει τυπογραφικά λάθη κ.λπ.
Σύνταξη
Είναι βολικό το ότι η PHP πριν την έκδοση 8 βλέπει τα attributes απλώς ως σχόλια, οπότε μπορούν να χρησιμοποιηθούν και σε κώδικα που πρέπει να λειτουργεί σε παλαιότερες εκδόσεις.
Η γραφή ενός μεμονωμένου attribute μοιάζει με
τη δημιουργία μιας περίπτωσης
αντικειμένου, αν παραλείψουμε τον τελεστή
new
. Δηλαδή, το όνομα της κλάσης
ακολουθούμενο από ορίσματα σε
παρενθέσεις:
#[Column('string', 32, true, false)]#
protected $username;
Και εδώ είναι το σημείο όπου μπορεί να χρησιμοποιηθεί το νέο καυτό χαρακτηριστικό της PHP 8.0 – τα ονομαστικά ορίσματα:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Κάθε στοιχείο μπορεί να έχει πολλαπλά attributes, τα οποία μπορούν να γραφτούν ξεχωριστά ή διαχωρισμένα με κόμμα:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
Το παρακάτω attribute ισχύει και για τις τρεις properties:
#[Common]
private $a, $b, $c;
Στις προεπιλεγμένες τιμές των properties μπορούν να χρησιμοποιηθούν απλές εκφράσεις και σταθερές, οι οποίες μπορούν να αξιολογηθούν κατά τη μεταγλώττιση, και το ίδιο ισχύει και για τα ορίσματα των attributes:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Δυστυχώς, η τιμή ενός ορίσματος δεν μπορεί να είναι ένα άλλο attribute, δηλαδή δεν μπορούν να ενσωματωθούν attributes. Για παράδειγμα, η ακόλουθη annotation που χρησιμοποιείται στο Doctrine δεν μπορεί να μετατραπεί εντελώς άμεσα σε attributes:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Επίσης, δεν υπάρχει αντίστοιχο attribute για το file-level phpDoc, δηλαδή το σχόλιο που βρίσκεται στην αρχή του αρχείου, το οποίο χρησιμοποιείται για παράδειγμα από το Nette Tester.
Ανάκτηση attributes
Ποια attributes έχουν τα μεμονωμένα στοιχεία
το μαθαίνουμε μέσω reflection. Οι κλάσεις reflection
διαθέτουν μια νέα μέθοδο getAttributes()
, η
οποία επιστρέφει έναν πίνακα αντικειμένων
ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // πλήρες όνομα του attribute, π.χ. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // επιστρέφει την περίπτωση new MyAttributes\Example('Hello', 123)
}
Τα επιστρεφόμενα attributes μπορούν να
φιλτραριστούν με μια παράμετρο, π.χ. το
$reflection->getAttributes(Example::class)
επιστρέφει
μόνο τα attributes Example
.
Κλάσεις attribute
Η κλάση του attribute MyAttributes\Example
δεν
χρειάζεται να υπάρχει. Η ύπαρξη απαιτείται
μόνο από την κλήση της μεθόδου
newInstance()
επειδή δημιουργεί την
περίπτωσή της. Ας τη γράψουμε λοιπόν. Θα
είναι μια εντελώς συνηθισμένη κλάση, απλά
πρέπει να δηλώσουμε σε αυτήν το attribute
Attribute
(δηλαδή από το καθολικό namespace
του συστήματος):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Μπορεί να περιοριστεί σε ποια γλωσσικά στοιχεία θα είναι νόμιμη η χρήση του attribute. Έτσι, για παράδειγμα, μόνο σε κλάσεις και properties:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Διαθέσιμα είναι τα flags TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
και το προεπιλεγμένο
TARGET_ALL
.
Αλλά προσοχή, η επαλήθευση της σωστής ή
λανθασμένης χρήσης γίνεται εκπληκτικά μόνο
κατά την κλήση της μεθόδου newInstance()
. Ο
ίδιος ο compiler δεν εκτελεί αυτόν τον έλεγχο.
Το μέλλον με τα attributes
Χάρη στα attributes και τους νέους
τύπους, τα σχόλια τεκμηρίωσης της PHP θα
γίνουν για πρώτη φορά στην ιστορία τους
πραγματικά μόνο σχόλια τεκμηρίωσης. Ήδη το
PhpStorm έρχεται με δικά
του attributes, τα οποία μπορούν να
αντικαταστήσουν, για παράδειγμα, την annotation
@deprecated
. Και μπορεί να υποτεθεί ότι
αυτό το attribute θα είναι κάποτε στάνταρ στην
PHP. Παρομοίως, θα αντικατασταθούν και άλλες
annotations, όπως @throws
κ.λπ.
Αν και το Nette χρησιμοποιεί annotations από την πρώτη του έκδοση για τη σήμανση persistent παραμέτρων και components, δεν υπήρξε μαζικότερη χρήση τους επειδή δεν ήταν εγγενής γλωσσική κατασκευή, οπότε οι editors δεν τις πρότειναν και ήταν εύκολο να γίνει λάθος σε αυτές. Αυτό σήμερα λύνεται με plugins για editors, αλλά ο πραγματικά εγγενής τρόπος που φέρνουν τα attributes ανοίγει εντελώς νέες δυνατότητες.
Παρεμπιπτόντως, τα attributes απέκτησαν
εξαίρεση στο Nette Coding Standard, το οποίο απαιτεί
το όνομα της κλάσης, εκτός από την
εξειδίκευση (π.χ. Product
, InvalidValue
),
να περιέχει και τη γενικότητα (δηλαδή
ProductPresenter
, InvalidValueException
).
Διαφορετικά, δεν θα ήταν σαφές κατά τη χρήση
στον κώδικα τι ακριβώς αντιπροσωπεύει η
κλάση. Στα attributes, αυτό αντίθετα δεν είναι
επιθυμητό, οπότε η κλάση ονομάζεται
Inject
αντί για InjectAttribute
.
Στο τελευταίο μέρος θα δούμε ποιες νέες συναρτήσεις και κλάσεις εμφανίστηκαν στην PHP και θα παρουσιάσουμε τον Just in Time Compiler.
Για να υποβάλετε ένα σχόλιο, συνδεθείτε