SmartObject
SmartObject a îmbunătățit comportamentul obiectelor în PHP timp de mulți ani. Începând cu PHP 8.4, toate funcționalitățile sale au devenit parte nativă a PHP-ului, completându-și astfel misiunea istorică de a fi pionierul 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ă pentru deficiențele modelului de obiecte din PHP la acea vreme. Într-o perioadă în care PHP suferea de numeroase probleme legate de design-ul orientat pe obiecte, acesta a adus îmbunătățiri semnificative și a simplificat munca dezvoltatorilor. A devenit o parte legendară a framework-ului Nette. A oferit funcționalități pe care PHP le-a obținut abia mulți ani mai târziu – de la validarea accesului la proprietăți până la gestionarea sofisticată a erorilor. Odată cu apariția PHP 8.4, și-a încheiat misiunea istorică, deoarece toate funcționalitățile sale au devenit părți native ale limbajului. A fost cu 17 ani înaintea dezvoltării PHP-ului.
SmartObject a trecut printr-o evoluție tehnică interesantă. Inițial, a fost implementat ca și clasă
Nette\Object
, de la care alte clase moșteneau funcționalitatea necesară. O schimbare semnificativă a venit cu
PHP 5.4, care a introdus suportul pentru trait-uri. Acest lucru a permis transformarea în trait-ul
Nette\SmartObject
, oferind mai multă flexibilitate – dezvoltatorii puteau folosi funcționalitatea chiar și în
clasele care moșteneau deja dintr-o altă clasă. În timp ce clasa originală Nette\Object
a încetat să existe
odată cu PHP 7.2 (care a interzis denumirea claselor cu cuvântul ‘Object’), trait-ul Nette\SmartObject
continuă să existe.
Să explorăm funcționalitățile pe care Nette\Object
și mai târziu Nette\SmartObject
le-au
oferit. Fiecare dintre aceste funcții a reprezentat un pas important înainte în programarea orientată pe obiecte în PHP.
Stări de eroare consistente
Una dintre cele mai presante 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 se comporta PHP-ul inițial:
echo $obj->undeclared; // E_NOTICE, mai târziu E_WARNING
$obj->undeclared = 1; // trece silențios fără avertisment
$obj->unknownMethod(); // Fatal error (imposibil de prins cu try/catch)
Fatal error termina aplicația fără posibilitatea de a reacționa. Scrierea silențioasă în membri inexistenți fără
avertisment putea duce la erori grave, greu de detectat. Nette\Object
prindea toate aceste cazuri și arunca
o excepție MemberAccessException
, permițând programatorilor să reacționeze și să gestioneze
aceste erori:
echo $obj->undeclared; // aruncă Nette\MemberAccessException
$obj->undeclared = 1; // aruncă Nette\MemberAccessException
$obj->unknownMethod(); // aruncă Nette\MemberAccessException
Din PHP 7.0, limbajul nu mai cauzează fatal error-uri imposibil de prins, iar din PHP 8.2, accesul la membri nedeclarați este considerat o eroare.
Ajutorul “Did you mean?”
Nette\Object
a venit cu o funcționalitate foarte convenabilă: sugestii inteligente pentru greșeli de scriere.
Când un dezvoltator făcea o greșeală în numele unei metode sau variabile, nu doar raporta eroarea, ci oferea și ajutor
sugerând numele corect. Acest mesaj iconic, cunoscut ca “did you mean?”, a economisit programatorilor ore întregi de
căutare a greșelilor de scriere:
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()?"
Deși PHP-ul în sine nu are nicio formă de “did you mean?”, această funcționalitate este acum oferită de Tracy. Poate chiar auto-corecta astfel de erori.
Properties cu acces controlat
O inovație semnificativă pe care SmartObject a adus-o în PHP au fost properties cu acces controlat. Acest concept, comun în limbaje precum C# sau Python, a permis dezvoltatorilor să controleze elegant accesul la datele obiectelor și să asigure consistența acestora. Properties sunt un instrument puternic al programării orientate pe obiecte. Funcționează ca variabile, dar sunt de fapt reprezentate prin metode (getteri și setteri). Acest lucru permite validarea intrărilor sau generarea valorilor în momentul citirii.
Pentru a utiliza properties, trebuie să:
- Adăugați adnotarea
@property <type> $xyz
la clasă - Creați un getter numit
getXyz()
sauisXyz()
, un setter numitsetXyz()
- Să vă asigurați că getter-ul și setter-ul sunt public sau protected. Sunt opționali – astfel pot exista ca properties read-only sau write-only
Să vedem un exemplu practic folosind clasa Circle, unde vom folosi properties pentru a ne asigura că raza este întotdeauna
non-negativă. Vom înlocui public $radius
cu o property:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // nu este public!
// getter pentru property-ul $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pentru property-ul $radius
protected function setRadius(float $radius): void
{
// sanitizăm valoarea înainte de salvare
$this->radius = max(0.0, $radius);
}
// getter pentru property-ul $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()
Din 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;
}
}
Extension methods
Nette\Object
a adus în PHP un alt concept interesant inspirat din limbajele de programare moderne – extension
methods. Această funcționalitate, împrumutată din C#, a permis dezvoltatorilor să extindă elegant clasele existente cu
metode noi fără a le modifica sau moșteni. De exemplu, puteați adăuga o metodă addDateTime()
unui formular
care adaugă un DateTimePicker personalizat:
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Extension methods s-au dovedit nepractice deoarece editoarele nu sugerau numele lor și în schimb raportau că metoda nu există. Prin urmare, suportul lor a fost întrerupt. Astăzi, este mai comun să se folosească compoziția sau moștenirea pentru a extinde funcționalitatea clasei.
Obținerea numelui clasei
SmartObject oferea o metodă simplă pentru obținerea numelui clasei:
$class = $obj->getClass(); // folosind Nette\Object
$class = $obj::class; // din PHP 8.0
Acces la Reflexie și Adnotări
Nette\Object
oferea acces la reflexie și adnotări prin metodele getReflection()
și
getAnnotation()
. Această abordare a simplificat semnificativ lucrul cu meta-informațiile claselor:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returnează 'John Doe'
Din PHP 8.0, este posibil să accesați meta-informațiile prin atribute, care oferă chiar mai multe posibilități și o verificare mai bună a tipurilor:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Method getteri
Nette\Object
oferea o modalitate elegantă de a transmite metode 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
Din PHP 8.1, puteți utiliza 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 din starea 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ătoarea buclă:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Pentru claritate, recomandăm evitarea metodei magice $this->onChange()
. O înlocuire practică este funcția
Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);