SmartObject

SmartObject je v preteklosti na različne načine popravljal obnašanje predmetov, vendar današnji PHP večino teh izboljšav že vključuje. Vendar pa še vedno dodaja podporo za lastnost.

Namestitev:

composer require nette/utils

Lastnosti, nastavljalci in pridobitelji

V sodobnih objektno usmerjenih jezikih (npr. C#, Python, Ruby, JavaScript) se izraz lastnost nanaša na posebne člane razredov, ki so videti kot spremenljivke, v resnici pa jih predstavljajo metode. Ko se vrednost te “spremenljivke” dodeli ali prebere, se pokliče ustrezna metoda (imenovana getter ali setter). To je zelo priročno, saj nam omogoča popoln nadzor nad dostopom do spremenljivk. Potrdimo lahko vnos ali ustvarimo rezultate samo takrat, ko je lastnost prebrana.

Lastnosti PHP niso podprte, vendar jih lahko posnemamo z lastnostmi Nette\SmartObject. Kako jo uporabiti?

  • V razred dodajte opombo v obliki @property <type> $xyz
  • Ustvarite getter z imenom getXyz() ali isXyz(), setter z imenom setXyz()
  • Getter in setter morata biti javna ali zaščitena in sta neobvezna, zato je lahko lastnost samo za branje ali samo za pisanje

Lastnost za razred Circle bomo uporabili za zagotovitev, da se v spremenljivko $radius vnesejo samo nenegativna števila. Zamenjajte public $radius z lastnostjo:

/**
 * @property float $radius
 * @property-read bool $visible
 */
class Circle
{
	use Nette\SmartObject;

	private float $radius = 0.0; // ni javno

	// getter za lastnost $radius
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// setter za lastnost $radius
	protected function setRadius(float $radius): void
	{
		// sanitizacija vrednosti pred shranjevanjem
		$this->radius = max(0.0, $radius);
	}

	// getter za lastnost $visible
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10;  // dejansko pokliče setRadius(10)
echo $circle->radius;  // pokliče getRadius()
echo $circle->visible; // pokliče isVisible()

Lastnosti so predvsem sintaktični sladkor, ki je namenjen temu, da programerju s poenostavitvijo kode osladi življenje. Če jih ne želite, vam jih ni treba uporabljati.

Pogled v zgodovino

SmartObject je v preteklosti na številne načine izboljševal obnašanje predmetov, vendar današnji PHP večino teh izboljšav že vključuje. Naslednje besedilo je nostalgičen pogled v zgodovino, ki nas spominja na razvoj stvari.

Objektni model PHP je že od samega začetka trpel zaradi številnih resnih pomanjkljivosti in pomanjkljivosti. To je privedlo do oblikovanja razreda Nette\Object (leta 2007), katerega cilj je bil odpraviti te težave in povečati udobje pri uporabi PHP. Vse, kar je bilo potrebno, je bilo, da so drugi razredi podedovali razred in pridobili prednosti, ki jih je ponujal. Ko je PHP 5.4 uvedel podporo za lastnosti, je bil razred Nette\Object nadomeščen z lastnostjo Nette\SmartObject. S tem se je odpravila potreba po dedovanju od skupnega prednika. Poleg tega je bilo mogoče lastnost uporabiti v razredih, ki so že dedovali od drugega razreda. Dokončen konec Nette\Object se je zgodil z izdajo PHP 7.2, ki je prepovedal, da bi se razredi imenovali Object.

Z nadaljnjim razvojem PHP sta se izboljšala njegov objektni model in jezikovne zmožnosti. Različne funkcije razreda SmartObject so postale nepotrebne. Od izdaje PHP 8.2 je ostala le ena funkcija, ki ni neposredno podprta v PHP: možnost uporabe tako imenovanih lastnosti.

Katere funkcije sta ponujala Nette\Object in posledično Nette\SmartObject? Tukaj je pregled. (V primerih je uporabljen razred Nette\Object, vendar večina lastnosti velja tudi za lastnost Nette\SmartObject ).

Nedosledne napake

PHP se je nedosledno obnašal pri dostopu do nereklariranih članov. Stanje v času Nette\Object je bilo naslednje:

echo $obj->undeclared; // E_NOTICE, pozneje E_WARNING
$obj->undeclared = 1;  // poteka tiho, brez poročanja
$obj->unknownMethod(); // usodna napaka (ki je ni mogoče ujeti s funkcijo try/catch)

Usodna napaka je prekinila aplikacijo brez možnosti odziva. Tiho pisanje v neobstoječe člene brez opozorila bi lahko privedlo do resnih napak, ki bi jih bilo težko odkriti. Nette\Object Vsi ti primeri so bili ujeti in zavržena je bila izjema MemberAccessException.

echo $obj->undeclared;   // vrgel Izjemo Nette\MemberAccessException
$obj->undeclared = 1;    // throw Nette\MemberAccessException
$obj->unknownMethod();   // throw Nette\MemberAccessException

Od PHP 7.0 PHP ne povzroča več usodnih napak, ki jih ni mogoče ujeti, dostop do nedeklariranih članov pa je napaka od PHP 8.2.

Ali ste mislili?

Če se je vrgla napaka Nette\MemberAccessException, morda zaradi tiskarske napake pri dostopu do predmetne spremenljivke ali klicu metode, je Nette\Object v sporočilu o napaki poskušal podati namig, kako napako odpraviti, in sicer v obliki ikoničnega dodatka “Ali ste mislili?”.

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()?"

Čeprav današnji PHP nima funkcije “Ali ste mislili?”, lahko Tracy napakam doda ta stavek. Takšne napake lahko celo samodejno popravi.

Metode razširitve

Navdih za razširitvene metode iz C#. Omogočale so dodajanje novih metod obstoječim razredom. Na primer, obrazcu lahko dodate metodo addDateTime() in tako dodate svoj DateTimePicker.

Form::extensionMethod(
	'addDateTime',
	fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);

$form = new Form;
$form->addDateTime('date');

Razširitvene metode so se izkazale za nepraktične, saj urejevalniki njihovih imen niso samodejno dopolnili, temveč so sporočili, da metoda ne obstaja. Zato je bila njihova podpora ukinjena.

Pridobivanje imena razreda

$class = $obj->getClass(); // z uporabo Nette\Object
$class = $obj::class;      // od PHP 8.0

Dostop do razmisleka in opomb

Nette\Object ponujen dostop do refleksije in anotacij z metodama getReflection() in getAnnotation():

/**
 * @author John Doe
 */
class Foo extends Nette\Object
{
}

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // vrne 'John Doe'

Od različice PHP 8.0 je mogoče dostopati do metainformacij v obliki atributov:

#[Author('John Doe')]
class Foo
{
}

$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];

Metode, ki pridobivajo

Nette\Object je ponujal eleganten način za ravnanje z metodami, kot da bi bile spremenljivke:

class Foo extends Nette\Object
{
	public function adder($a, $b)
	{
		return $a + $b;
	}
}

$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5

Od različice PHP 8.1 lahko uporabljate tako imenovano sintakso “first-class callable syntax”::https://www.php.net/…lable_syntax

$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5

Dogodki

Nette\Object ponujen sintaktični sladkor za sprožitev dogodka:

class Circle extends Nette\Object
{
	public array $onChange = [];

	public function setRadius(float $radius): void
	{
		$this->onChange($this, $radius);
		$this->radius = $radius;
	}
}

Koda $this->onChange($this, $radius) je enakovredna naslednjemu:

foreach ($this->onChange as $callback) {
	$callback($this, $radius);
}

Zaradi jasnosti priporočamo, da se izognete čarobni metodi $this->onChange(). Praktično nadomestilo je funkcija Nette\Utils\Arrays::invoke:

Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);
različica: 4.0