SmartObject și StaticClass
SmartObject adaugă suport pentru proprietăți la clasele PHP. StaticClass este utilizat pentru a desemna clasele statice.
Instalare:
composer require nette/utils
Proprietăți, Getters și Setters
În limbajele moderne orientate pe obiecte (de exemplu, C#, Python, Ruby, JavaScript), termenul proprietate se referă la membrii speciali ai claselor care seamănă cu variabilele, dar care sunt de fapt reprezentate de metode. Atunci când valoarea acestei “variabile” este atribuită sau citită, se apelează metoda corespunzătoare (numită getter sau setter). Acesta este un lucru foarte util, deoarece ne oferă un control total asupra accesului la variabile. Putem valida intrarea sau genera rezultate numai atunci când proprietatea este citită.
Proprietățile PHP nu sunt acceptate, dar trait Nette\SmartObject
le poate imita. Cum se utilizează?
- Adăugați o adnotare la clasă sub forma
@property <type> $xyz
- Creați un getter numit
getXyz()
sauisXyz()
, un setter numitsetXyz()
- Getterul și setterul trebuie să fie public sau protected și sunt opționale, astfel încât poate exista o proprietate read-only sau write-only.
Vom utiliza proprietatea pentru clasa Circle pentru a ne asigura că în variabila $radius
sunt introduse numai
numere non-negative. Înlocuiți public $radius
cu proprietatea:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // nu sunt publice
// getter pentru proprietatea $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pentru proprietatea $radius
protected function setRadius(float $radius): void
{
// curățarea valorii înainte de a o salva
$this->radius = max(0.0, $radius);
}
// getter pentru proprietatea $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // apelează de fapt setRadius(10)
echo $circle->radius; // apelează getRadius()
echo $circle->visible; // solicită isVisible()
Proprietățile sunt în primul rând zahăr sintactic, care are rolul de a face viața programatorului mai ușoară prin simplificarea codului. Dacă nu le doriți, nu trebuie să le folosiți.
Clase statice
Clasele statice, adică clasele care nu sunt destinate a fi instanțiate, pot fi marcate cu trăsătura
Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Atunci când încercați să creați o instanță, se aruncă excepția Error
, indicând că clasa este
statică.
O privire în istorie
SmartObject obișnuia să îmbunătățească și să corecteze comportamentul clasei în multe feluri, dar evoluția PHP a făcut ca majoritatea caracteristicilor originale să fie redundante. Așadar, în cele ce urmează este o privire în istoria evoluției lucrurilor.
Încă de la început, modelul de obiecte PHP a suferit de o serie de defecte și ineficiențe grave. Acesta a fost motivul
pentru crearea clasei Nette\Object
(în 2007), care a încercat să le remedieze și să îmbunătățească
experiența de utilizare a PHP. A fost suficient pentru ca alte clase să moștenească din ea și să obțină beneficiile pe
care le-a adus. Când PHP 5.4 a venit cu suport pentru trăsături, clasa Nette\Object
a fost înlocuită cu
Nette\SmartObject
. Astfel, nu a mai fost necesară moștenirea de la un strămoș comun. În plus, trait putea fi
utilizat în clase care moșteneau deja de la o altă clasă. Sfârșitul final al Nette\Object
a venit odată cu
lansarea PHP 7.2, care a interzis ca clasele să fie denumite Object
.
Pe măsură ce dezvoltarea PHP a continuat, modelul de obiecte și capacitățile limbajului au fost îmbunătățite.
Funcțiile individuale ale clasei SmartObject
au devenit redundante. De la lansarea PHP 8.2, singura caracteristică
rămasă care nu este încă direct suportată în PHP este capacitatea de a utiliza așa-numitele proprietăți.
Ce caracteristici ofereau cândva Nette\Object
și Nette\Object
? Iată o prezentare generală.
(Exemplele folosesc clasa Nette\Object
, dar majoritatea proprietăților se aplică și la trăsătura
Nette\SmartObject
).
Erori inconsistente
PHP avea un comportament inconsecvent la accesarea membrilor nedeclarați. Starea la momentul Nette\Object
era
următoarea:
echo $obj->undeclared; // E_NOTICE, ulterior E_WARNING
$obj->undeclared = 1; // trece în tăcere fără a raporta
$obj->unknownMethod(); // Eroare fatală (care nu poate fi prinsă prin try/catch)
O eroare fatală a încheiat aplicația fără nicio posibilitate de reacție. Scrierea silențioasă în membri inexistenți
fără avertisment putea duce la erori grave, greu de detectat. Nette\Object
Toate aceste cazuri au fost detectate
și a fost lansată o excepție MemberAccessException
.
echo $obj->undeclared; // aruncați Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
Începând cu PHP 7.0, PHP nu mai provoacă erori fatale care nu pot fi prinse, iar accesarea membrilor nedeclarați este un bug începând cu PHP 8.2.
Ați vrut să spuneți?
În cazul în care se producea o eroare Nette\MemberAccessException
, poate din cauza unei greșeli de scriere la
accesarea unei variabile de obiect sau la apelarea unei metode, Nette\Object
încerca să ofere un indiciu în
mesajul de eroare cu privire la modul de corectare a erorii, sub forma unui addendum iconic “did you mean?”.
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()?"
Este posibil ca PHP-ul de astăzi să nu aibă nicio formă de “ai vrut să spui?”, dar Tracy adaugă acest addendum la erori. Și poate chiar să corecteze singur astfel de erori.
Metode de extensie
Inspirat de metodele de extensie din C#. Acestea au oferit posibilitatea de a adăuga noi metode la clasele existente. De
exemplu, ați putea adăuga metoda addDateTime()
la un formular pentru a adăuga propriul DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Metodele de extensie s-au dovedit a fi nepractice deoarece numele lor nu era completat automat de către editori, în schimb aceștia raportau că metoda nu există. Prin urmare, suportul lor a fost întrerupt.
Obținerea numelui clasei
$class = $obj->getClass(); // folosind Nette\Object
$class = $obj::class; // din PHP 8.0
Acces la reflecție și adnotări
Nette\Object
a oferit acces la reflectare și adnotare prin intermediul metodelor getReflection()
și
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returnează "John Doe
Începând cu PHP 8.0, este posibilă accesarea meta-informațiilor sub formă de atribute:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Metode Getters
Nette\Object
a oferit o modalitate elegantă de a trata metodele ca și cum ar fi fost variabile:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Începând cu PHP 8.1, puteți utiliza așa-numita sintaxă de primă clasă pentru metode apelabile:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Evenimente
Nette\Object
a oferit zahăr sintactic pentru a declanșa evenimentul:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius
}
}
Codul $this->onChange($this, $radius)
este echivalent cu următoarele:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Din motive de claritate, vă recomandăm să evitați metoda magică $this->onChange()
. Un substitut practic
este funcția Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);