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() sau isXyz(), un setter numit setXyz()
  • 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);
versiune: 4.0