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() 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.

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);
versiune: 4.0