Nette Documentation Preview

syntax
SmartObject
***********

.[perex]
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:

```shell
composer require nette/utils
```


Lastnosti, nastavljalci in pridobitelji .[#toc-properties-getters-and-setters]
==============================================================================

V sodobnih objektno usmerjenih jezikih (npr. C#, Python, Ruby, JavaScript) se izraz *lastnost* nanaša na [posebne člane razredov |https://en.wikipedia.org/wiki/Property_(programming)], 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:

```php
/**
 * @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"((sintactic sugar)), ki je namenjen temu, da programerju s poenostavitvijo kode osladi življenje. Če jih ne želite, vam jih ni treba uporabljati.


Pogled v zgodovino .[#toc-a-glimpse-into-history]
=================================================

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 |#Properties, getters, and setters].

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 .[#toc-inconsistent-errors]
---------------------------------------------
PHP se je nedosledno obnašal pri dostopu do nereklariranih članov. Stanje v času `Nette\Object` je bilo naslednje:

```php
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`.

```php
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? .[#toc-did-you-mean]
-------------------------------------
Č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?".

```php
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 |tracy:] napakam doda ta stavek. [Takšne napake |tracy:open-files-in-ide#toc-demos] lahko celo [samodejno popravi |tracy:open-files-in-ide#toc-demos].


Metode razširitve .[#toc-extension-methods]
-------------------------------------------
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.

```php
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 .[#toc-getting-the-class-name]
---------------------------------------------------------

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


Dostop do razmisleka in opomb .[#toc-access-to-reflection-and-annotations]
--------------------------------------------------------------------------

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

```php
/**
 * @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:

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

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


Metode, ki pridobivajo .[#toc-method-getters]
---------------------------------------------

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

```php
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/manual/en/functions.first_class_callable_syntax

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


Dogodki .[#toc-events]
----------------------

`Nette\Object` ponujen sintaktični sladkor za sprožitev [dogodka |nette:glossary#events]:

```php
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:

```php
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 |arrays#invoke]:

```php
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);
```

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);