SmartObject
SmartObject a îmbunătățit comportamentul obiectelor în PHP timp de ani de zile. Începând cu versiunea PHP 8.4, toate funcțiile sale sunt deja parte a PHP însuși, încheindu-și astfel misiunea istorică de a fi pionier al abordării moderne orientate pe obiecte în PHP.
Instalare:
composer require nette/utils
SmartObject a apărut în 2007 ca o soluție revoluționară la deficiențele modelului de obiecte PHP de atunci. Într-o perioadă în care PHP suferea de numeroase probleme de design de obiecte, a adus o îmbunătățire semnificativă și o simplificare a muncii pentru dezvoltatori. A devenit o parte legendară a framework-ului Nette. Oferea funcționalități pe care PHP le-a dobândit abia mulți ani mai târziu – de la controlul accesului la proprietățile obiectelor până la zahăr sintactic sofisticat. Odată cu apariția PHP 8.4, și-a încheiat misiunea istorică, deoarece toate funcțiile sale au devenit parte nativă a limbajului. A devansat dezvoltarea PHP cu remarcabili 17 ani.
Din punct de vedere tehnic, SmartObject a trecut printr-o evoluție interesantă. Inițial a fost implementat ca o clasă
Nette\Object
, de la care celelalte clase moșteneau funcționalitatea necesară. O schimbare fundamentală a venit
cu PHP 5.4, care a introdus suportul pentru trait-uri. Acest lucru a permis transformarea în forma trait-ului
Nette\SmartObject
, ceea ce a adus o flexibilitate mai mare – dezvoltatorii puteau utiliza funcționalitatea chiar
și în clasele care moșteneau deja de la o altă clasă. În timp ce clasa originală Nette\Object
a dispărut
odată cu apariția PHP 7.2 (care a interzis denumirea claselor cu cuvântul Object
), trait-ul
Nette\SmartObject
trăiește în continuare.
Să trecem în revistă caracteristicile pe care Nette\Object
și, mai târziu, Nette\SmartObject
le-au oferit odată. Fiecare dintre aceste funcții, la vremea sa, a reprezentat un pas semnificativ înainte în domeniul
programării orientate pe obiecte în PHP.
Stări de eroare consistente
Una dintre cele mai arzătoare probleme ale PHP-ului timpuriu a fost comportamentul inconsistent în lucrul cu obiectele.
Nette\Object
a adus ordine și predictibilitate în acest haos. Să vedem cum arăta comportamentul original
al PHP:
echo $obj->undeclared; // E_NOTICE, mai târziu E_WARNING
$obj->undeclared = 1; // trece silențios fără raportare
$obj->unknownMethod(); // Eroare fatală (neinterceptabilă cu try/catch)
Eroarea fatală încheia aplicația fără nicio posibilitate de a reacționa. Scrierea silențioasă în membrii inexistenți
fără avertisment putea duce la erori grave, greu de detectat. Nette\Object
intercepta toate aceste cazuri și
arunca excepția MemberAccessException
, permițând programatorilor să reacționeze la erori și să le rezolve.
echo $obj->undeclared; // aruncă Nette\MemberAccessException
$obj->undeclared = 1; // aruncă Nette\MemberAccessException
$obj->unknownMethod(); // aruncă Nette\MemberAccessException
Începând cu PHP 7.0, limbajul nu mai provoacă erori fatale neinterceptabile, iar de la PHP 8.2, accesul la membrii nedeclarați este considerat o eroare.
Ajutor “Did you mean?”
Nette\Object
a venit cu o funcție foarte plăcută: ajutor inteligent pentru greșelile de tipar. Când un
dezvoltator făcea o greșeală în numele unei metode sau variabile, nu numai că raporta eroarea, dar oferea și o mână de
ajutor sub forma unei sugestii a numelui corect. Acest mesaj iconic, cunoscut sub numele de “did you mean?”, a economisit
programatorilor ore întregi de căutare a greșelilor de tipar:
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// aruncă Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
PHP-ul de astăzi nu are nicio formă de „did you mean?”, dar această adăugire poate fi completată în erori de către Tracy. Și chiar corectează automat astfel de erori.
Proprietăți cu acces controlat
O inovație semnificativă adusă de SmartObject în PHP au fost proprietățile cu acces controlat. Acest concept, comun în limbaje precum C# sau Python, a permis dezvoltatorilor să controleze elegant accesul la datele obiectului și să asigure consistența acestora. Proprietățile sunt un instrument puternic al programării orientate pe obiecte. Funcționează ca variabile, dar în realitate sunt reprezentate de metode (gettere și settere). Acest lucru permite validarea intrărilor sau generarea valorilor doar în momentul citirii.
Pentru a utiliza proprietățile, trebuie să:
- Adăugați clasei o adnotare de forma
@property <type> $xyz
- Creați un getter cu numele
getXyz()
sauisXyz()
, un setter cu numelesetXyz()
- Asigurați-vă că getter-ul și setter-ul sunt public sau protected. Sunt opționale – pot exista deci ca proprietăți read-only sau write-only
Să vedem un exemplu practic cu clasa Circle, unde vom folosi proprietățile pentru a ne asigura că raza este întotdeauna un
număr non-negativ. Vom înlocui public $radius
original cu o proprietate:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // nu este public!
// getter pentru proprietatea $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pentru proprietatea $radius
protected function setRadius(float $radius): void
{
// sanitizăm valoarea înainte de salvare
$this->radius = max(0.0, $radius);
}
// getter pentru proprietatea $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // de fapt apelează setRadius(10)
echo $circle->radius; // apelează getRadius()
echo $circle->visible; // apelează isVisible()
Începând cu PHP 8.4, aceeași funcționalitate poate fi obținută folosind property hooks, care oferă o sintaxă mult mai elegantă și concisă:
class Circle
{
public float $radius = 0.0 {
set => max(0.0, $value);
}
public bool $visible {
get => $this->radius > 0;
}
}
Metode de extensie
Nette\Object
a adus în PHP un alt concept interesant inspirat de limbajele de programare moderne – metodele de
extensie. Această funcție, preluată din C#, a permis dezvoltatorilor să extindă elegant clasele existente cu noi metode
fără a fi nevoie să le modifice sau să moștenească de la ele. De exemplu, ați putea adăuga la formular o metodă
addDateTime()
care adaugă un DateTimePicker personalizat:
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 erau sugerate de editori, dimpotrivă, raportau că metoda nu există. De aceea, suportul lor a fost întrerupt. Astăzi, este mai comun să se utilizeze compoziția sau moștenirea pentru extinderea funcționalității claselor.
Obținerea numelui clasei
Pentru a obține numele clasei, SmartObject oferea o metodă simplă:
$class = $obj->getClass(); // folosind Nette\Object
$class = $obj::class; // de la PHP 8.0
Acces la reflecție și adnotări
Nette\Object
oferea acces la reflecție și adnotări prin metodele getReflection()
și
getAnnotation()
. Acest acces a simplificat semnificativ lucrul cu metainformațiile claselor:
/**
* @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 să accesați metainformații sub formă de atribute, care oferă și mai multe posibilități și un control mai bun al tipurilor:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Getter-e de metode
Nette\Object
oferea o modalitate elegantă de a transmite metode ca și cum ar fi 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, este posibil să utilizați așa-numita first-class callable syntax, care duce acest concept și mai departe:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Evenimente
SmartObject oferă o sintaxă simplificată pentru lucrul cu evenimente. Evenimentele permit obiectelor să informeze alte părți ale aplicației despre schimbările stării lor:
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ătorul ciclu:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Din motive de claritate, recomandăm evitarea metodei magice $this->onChange()
. Un înlocuitor practic este, de
exemplu, funcția Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);