SmartObject
Το SmartObject διόρθωνε τη συμπεριφορά των αντικειμένων με πολλούς τρόπους, αλλά η σημερινή PHP περιλαμβάνει ήδη τις περισσότερες από αυτές τις βελτιώσεις εγγενώς. Ωστόσο, εξακολουθεί να προσθέτει υποστήριξη για property.
Εγκατάσταση:
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()
Οι ιδιότητες είναι κατά κύριο λόγο συντακτική ζάχαρη, η οποία έχει σκοπό να κάνει τη ζωή του προγραμματιστή πιο γλυκιά απλοποιώντας τον κώδικα. Αν δεν τις θέλετε, δεν χρειάζεται να τις χρησιμοποιήσετε.
Μια ματιά στην ιστορία
Το SmartObject χρησιμοποιείται για να βελτιώσει τη συμπεριφορά των αντικειμένων με πολλούς τρόπους, αλλά η σημερινή PHP ενσωματώνει ήδη τις περισσότερες από αυτές τις βελτιώσεις εγγενώς. Το κείμενο που ακολουθεί είναι μια νοσταλγική αναδρομή στην ιστορία, υπενθυμίζοντάς μας πώς εξελίχθηκαν τα πράγματα.
Από την αρχή της δημιουργίας του, το μοντέλο αντικειμένων της PHP
υπέφερε από μυριάδες σοβαρές ελλείψεις και αδυναμίες. Αυτό οδήγησε στη
δημιουργία της κλάσης Nette\Object
(το 2007), η οποία είχε ως στόχο να
διορθώσει αυτά τα προβλήματα και να βελτιώσει την άνεση της χρήσης της
PHP. Το μόνο που χρειαζόταν ήταν άλλες κλάσεις να κληρονομήσουν από
αυτήν και θα αποκτούσαν τα οφέλη που προσέφερε. Όταν η PHP 5.4 εισήγαγε
την υποστήριξη για γνωρίσματα, η κλάση Nette\Object
αντικαταστάθηκε
από το γνώρισμα Nette\SmartObject
. Αυτό εξάλειψε την ανάγκη να
κληρονομήσετε από έναν κοινό πρόγονο. Επιπλέον, το γνώρισμα μπορούσε
να χρησιμοποιηθεί σε κλάσεις που ήδη κληρονομούσαν από μια άλλη κλάση.
Το οριστικό τέλος του Nette\Object
ήρθε με την κυκλοφορία της PHP 7.2, η
οποία απαγόρευσε στις κλάσεις να ονομάζονται Object
.
Καθώς η ανάπτυξη της PHP συνεχιζόταν, το μοντέλο αντικειμένων και οι
δυνατότητες της γλώσσας βελτιώνονταν. Διάφορες λειτουργίες της κλάσης
SmartObject
έγιναν περιττές. Από την έκδοση της PHP 8.2, παραμένει μόνο
ένα χαρακτηριστικό που δεν υποστηρίζεται άμεσα στην PHP: η δυνατότητα
χρήσης των λεγόμενων ιδιοτήτων.
Ποια χαρακτηριστικά προσέφερε η Nette\Object
και, κατ' επέκταση, η
Nette\SmartObject
; Ακολουθεί μια επισκόπηση. (Στα παραδείγματα
χρησιμοποιείται η κλάση 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 δεν διαθέτει τη δυνατότητα “εννοούσατε;”, αυτή η φράση μπορεί να προστεθεί στα σφάλματα από την 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);