SmartObject και StaticClass
Το SmartObject προσθέτει υποστήριξη για ιδιότητες στις κλάσεις PHP. Το StaticClass χρησιμοποιείται για να δηλώσει στατικές κλάσεις.
Εγκατάσταση:
composer require nette/utils
Ιδιότητες, Getters και Setters
Στις σύγχρονες αντικειμενοστραφείς γλώσσες (π.χ. C#, Python, Ruby, JavaScript), ο όρος ιδιότητα αναφέρεται σε ειδικά μέλη των κλάσεων που μοιάζουν με μεταβλητές αλλά στην πραγματικότητα αντιπροσωπεύονται από μεθόδους. Όταν η τιμή αυτής της “μεταβλητής” εκχωρείται ή διαβάζεται, καλείται η αντίστοιχη μέθοδος (που ονομάζεται getter ή setter). Αυτό είναι κάτι πολύ βολικό, μας δίνει πλήρη έλεγχο της πρόσβασης στις μεταβλητές. Μπορούμε να επικυρώσουμε την είσοδο ή να δημιουργήσουμε αποτελέσματα μόνο όταν διαβάζεται η ιδιότητα.
Οι ιδιότητες της PHP δεν υποστηρίζονται, αλλά το trait Nette\SmartObject
μπορεί να τις μιμηθεί. Πώς να το χρησιμοποιήσετε;
- Προσθέστε ένα σχόλιο στην κλάση με τη μορφή
@property <type> $xyz
- Δημιουργήστε έναν getter με όνομα
getXyz()
ήisXyz()
, έναν setter με όνομαsetXyz()
- Ο getter και ο setter πρέπει να είναι δημόσιος ή προστατευμένος και είναι προαιρετικοί, οπότε μπορεί να υπάρχει μια ιδιότητα read-only ή write-only.
Θα χρησιμοποιήσουμε την ιδιότητα για την κλάση Circle για να
διασφαλίσουμε ότι μόνο μη αρνητικοί αριθμοί μπαίνουν στη μεταβλητή
$radius
. Αντικαταστήστε το public $radius
με το property:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // όχι δημόσια
// getter για την ιδιότητα $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter για την ιδιότητα $radius
protected function setRadius(float $radius): void
{
// εξυγίανση της τιμής πριν από την αποθήκευση
$this->radius = max(0.0, $radius);
}
// getter για την ιδιότητα $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // στην πραγματικότητα καλεί την setRadius(10)
echo $circle->radius; // καλεί την getRadius()
echo $circle->visible; // καλεί την isVisible()
Οι ιδιότητες είναι κατά κύριο λόγο συντακτική ζάχαρη, η οποία έχει σκοπό να κάνει τη ζωή του προγραμματιστή πιο γλυκιά απλοποιώντας τον κώδικα. Αν δεν τις θέλετε, δεν χρειάζεται να τις χρησιμοποιήσετε.
Στατικές κλάσεις
Οι στατικές κλάσεις, δηλαδή οι κλάσεις που δεν προορίζονται για
ενσάρκωση, μπορούν να επισημανθούν με το χαρακτηριστικό
Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Όταν προσπαθείτε να δημιουργήσετε ένα instance, εκπέμπεται η εξαίρεση
Error
, υποδεικνύοντας ότι η κλάση είναι στατική.
Μια ματιά στην ιστορία
Το SmartObject βελτίωνε και διόρθωνε τη συμπεριφορά των κλάσεων με πολλούς τρόπους, αλλά η εξέλιξη της PHP έχει καταστήσει τα περισσότερα από τα αρχικά χαρακτηριστικά περιττά. Έτσι, το παρακάτω είναι μια ματιά στην ιστορία της εξέλιξης των πραγμάτων.
Από την αρχή, το μοντέλο αντικειμένων της PHP υπέφερε από μια σειρά
σοβαρών ελαττωμάτων και αναποτελεσματικότητας. Αυτός ήταν ο λόγος για
τη δημιουργία της κλάσης Nette\Object
(το 2007), η οποία προσπάθησε να
τις διορθώσει και να βελτιώσει την εμπειρία χρήσης της PHP. Ήταν αρκετό
για να κληρονομήσουν και άλλες κλάσεις από αυτήν και να αποκτήσουν τα
οφέλη που έφερε. Όταν η PHP 5.4 ήρθε με υποστήριξη γνωρισμάτων, η κλάση
Nette\Object
αντικαταστάθηκε από την Nette\SmartObject
. Έτσι, δεν ήταν
πλέον απαραίτητη η κληρονομικότητα από έναν κοινό πρόγονο. Επιπλέον,
το trait μπορούσε να χρησιμοποιηθεί σε κλάσεις που ήδη κληρονομούσαν από
μια άλλη κλάση. Το οριστικό τέλος της Nette\Object
ήρθε με την
κυκλοφορία της PHP 7.2, η οποία απαγόρευσε στις κλάσεις να έχουν το όνομα
Object
.
Καθώς η ανάπτυξη της PHP προχωρούσε, το μοντέλο αντικειμένων και οι
δυνατότητες της γλώσσας βελτιώνονταν. Οι επιμέρους λειτουργίες της
κλάσης SmartObject
έγιναν περιττές. Από την έκδοση της PHP 8.2, το μόνο
χαρακτηριστικό που παραμένει και δεν υποστηρίζεται ακόμη άμεσα από
την PHP είναι η δυνατότητα χρήσης των λεγόμενων ιδιοτήτων.
Ποια χαρακτηριστικά προσέφεραν κάποτε οι Nette\Object
και
Nette\Object
; Ακολουθεί μια επισκόπηση. (Τα παραδείγματα
χρησιμοποιούν την κλάση Nette\Object
, αλλά οι περισσότερες από τις
ιδιότητες ισχύουν και για την ιδιότητα Nette\SmartObject
).
Ασυνεπή σφάλματα
Η PHP είχε ασυνεπή συμπεριφορά κατά την πρόσβαση σε μη δηλωμένα μέλη. Η
κατάσταση κατά τη στιγμή του Nette\Object
ήταν η εξής:
echo $obj->undeclared; // E_NOTICE, αργότερα E_WARNING
$obj->undeclared = 1; // περνάει σιωπηλά χωρίς αναφορά
$obj->unknownMethod(); // Μοιραίο σφάλμα (δεν μπορεί να πιαστεί από try/catch)
Το μοιραίο σφάλμα τερμάτισε την εφαρμογή χωρίς καμία δυνατότητα
αντίδρασης. Η σιωπηλή εγγραφή σε ανύπαρκτα μέλη χωρίς προειδοποίηση θα
μπορούσε να οδηγήσει σε σοβαρά σφάλματα που ήταν δύσκολο να
εντοπιστούν. Nette\Object
Όλες αυτές οι περιπτώσεις εντοπίστηκαν και
απορρίφθηκε μια εξαίρεση MemberAccessException
.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
Από την PHP 7.0, η PHP δεν προκαλεί πλέον μη πιασμένα μοιραία σφάλματα, ενώ η πρόσβαση σε μη δηλωμένα μέλη αποτελεί σφάλμα από την PHP 8.2.
Εννοείτε?
Εάν εμφανιζόταν ένα σφάλμα Nette\MemberAccessException
, ίσως λόγω
τυπογραφικού λάθους κατά την προσπέλαση μιας μεταβλητής αντικειμένου
ή την κλήση μιας μεθόδου, το Nette\Object
προσπαθούσε να δώσει μια
υπόδειξη στο μήνυμα σφάλματος για το πώς να διορθωθεί το σφάλμα, με τη
μορφή της εικονικής προσθήκης “εννοούσες;”.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// throw Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Η σημερινή PHP μπορεί να μην έχει καμία μορφή του “did you mean?”, αλλά η Tracy προσθέτει αυτή την προσθήκη στα σφάλματα. Και μπορεί ακόμη και να διορθώσει η ίδια τέτοια σφάλματα.
Μέθοδοι επέκτασης
Εμπνευσμένο από τις μεθόδους επέκτασης της C#. Έδιναν τη δυνατότητα
προσθήκης νέων μεθόδων σε υπάρχουσες κλάσεις. Για παράδειγμα, θα
μπορούσατε να προσθέσετε τη μέθοδο addDateTime()
σε μια φόρμα για να
προσθέσετε το δικό σας DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Οι μέθοδοι επέκτασης αποδείχθηκαν ανεφάρμοστες επειδή τα ονόματά τους δεν συμπληρώνονταν αυτόματα από τους συντάκτες, αλλά ανέφεραν ότι η μέθοδος δεν υπήρχε. Ως εκ τούτου, η υποστήριξή τους διακόπηκε.
Λήψη του ονόματος της κλάσης
$class = $obj->getClass(); // χρησιμοποιώντας το Nette\Object
$class = $obj::class; // από την PHP 8.0
Πρόσβαση στον Αναστοχασμό και τις Σημειώσεις
Nette\Object
προσφέρεται πρόσβαση στον προβληματισμό και τον
σχολιασμό με τη χρήση των μεθόδων getReflection()
και
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe
Από την PHP 8.0, είναι δυνατή η πρόσβαση σε μετα-πληροφορίες με τη μορφή χαρακτηριστικών:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Μέθοδοι Getters
Nette\Object
προσέφερε έναν κομψό τρόπο για να χειρίζεστε τις
μεθόδους σαν να ήταν μεταβλητές:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Από την PHP 8.1, μπορείτε να χρησιμοποιήσετε τη λεγόμενη σύνταξη κλήσης πρώτης κατηγορίας:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Γεγονότα
Nette\Object
προσφέρεται συντακτική ζάχαρη για την ενεργοποίηση
του συμβάντος:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius
}
}
Ο κώδικας $this->onChange($this, $radius)
είναι ισοδύναμος με τα
ακόλουθα:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Για λόγους σαφήνειας συνιστούμε να αποφύγετε τη μαγική μέθοδο
$this->onChange()
. Ένα πρακτικό υποκατάστατο είναι η συνάρτηση Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);