SmartObject
SmartObject obișnuia să corecteze comportamentul obiectelor în mai multe moduri, dar PHP-ul de astăzi include deja majoritatea acestor îmbunătățiri în mod nativ. Cu toate acestea, se adaugă încă suport pentru proprietate.
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.
O privire în istorie
SmartObject obișnuia să rafineze comportamentul obiectelor în numeroase moduri, dar PHP-ul de astăzi încorporează deja majoritatea acestor îmbunătățiri în mod nativ. Următorul text este o privire nostalgică asupra istoriei, amintindu-ne cum au evoluat lucrurile.
Încă de la începuturile sale, modelul de obiecte din PHP a suferit de o multitudine de deficiențe și neajunsuri grave.
Acest lucru a dus la crearea clasei Nette\Object
(în 2007), care a avut ca scop rectificarea acestor probleme și
îmbunătățirea confortului de utilizare a PHP. Tot ceea ce era necesar era ca alte clase să moștenească din ea și să
obțină beneficiile oferite de aceasta. Când PHP 5.4 a introdus suportul pentru trăsături, clasa Nette\Object
a
fost înlocuită cu trăsătura Nette\SmartObject
. Acest lucru a eliminat necesitatea de a moșteni de la un
strămoș comun. În plus, trăsătura putea fi utilizată în clase care moșteneau deja de la o altă clasă. Sfârșitul
definitiv 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 său de obiecte și capacitățile limbajului s-au îmbunătățit. Diverse
funcții ale clasei SmartObject
au devenit redundante. De la lansarea PHP 8.2, a rămas o singură funcție care nu
este direct suportată în PHP: capacitatea de a utiliza așa-numitele proprietăți.
Ce caracteristici au oferit Nette\Object
și, prin extensie, Nette\SmartObject
? Iată o prezentare
generală. (În exemple, este utilizată clasa Nette\Object
, dar majoritatea caracteristicilor se aplică și
trăsăturii 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()?"
În timp ce PHP-ul actual nu are o funcție “ai vrut să spui?”, această frază poate fi adăugată la erori de către Tracy. Acesta poate chiar să corecteze automat 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);