Nette Documentation Preview

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

.[perex]
SmartObject poprawiał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Wciąż jednak dodaje wsparcie dla *property*.


Instalacja:

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


Właściwości, gettery i settery .[#toc-properties-gettery-a-settery]
===================================================================

W nowoczesnych językach obiektowych (np. C#, Python, Ruby, JavaScript) termin *property* odnosi się do [specjalnych członków klas |https://en.wikipedia.org/wiki/Property_(programming)], które wyglądają jak zmienne, ale w rzeczywistości są reprezentowane przez metody. Kiedy wartość tej "zmiennej" jest przypisywana lub odczytywana, wywoływana jest odpowiednia metoda (zwana getterem lub setterem). Jest to bardzo przydatna rzecz, daje nam pełną kontrolę nad dostępem do zmiennych. Możemy zatwierdzić dane wejściowe lub wygenerować wyniki tylko wtedy, gdy właściwość zostanie odczytana.

Właściwości PHP nie są obsługiwane, ale traita `Nette\SmartObject` może je imitować. Jak to zrobić?

- Dodaj adnotację do klasy w postaci `@property <type> $xyz`
- Utwórz getter o nazwie `getXyz()` lub `isXyz()`, setter o nazwie `setXyz()`
- Getter i setter muszą być *public* lub *protected* i są opcjonalne, więc może być właściwość *read-only* lub *write-only*.

Wykorzystamy własność dla klasy Circle, aby zapewnić, że do zmiennej `$radius` wstawiane są tylko liczby nieujemne. Zamień `public $radius` na własność:

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

	private float $radius = 0.0; // není publiczne!

	// getter pro właściwość $radius
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// setter pro właściwość $radius
	protected function setRadius(float $radius): void
	{
		// hodnotu před uložením sanitizujeme
		$this->radius = max(0.0, $radius);
	}

	// getter pro właściwość $visible
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10;  // ve skutečnosti volá setRadius(10)
echo $circle->radius;  // volá getRadius()
echo $circle->visible; // volá isVisible()
```

Właściwości to przede wszystkim "cukier syntaktyczny"((syntactic sugar)), który ma uczynić życie programisty słodszym poprzez uproszczenie kodu. Jeśli ich nie chcesz, nie musisz z nich korzystać.


Rzut oka na historię .[#toc-a-glimpse-into-history]
===================================================

SmartObject udoskonalał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Poniższy tekst jest nostalgicznym spojrzeniem wstecz na historię, przypominającym nam, jak rzeczy ewoluowały.

Od samego początku model obiektowy PHP cierpiał na niezliczoną ilość poważnych niedociągnięć i braków. Doprowadziło to do stworzenia klasy `Nette\Object` (w 2007 roku), która miała na celu naprawienie tych problemów i zwiększenie komfortu korzystania z PHP. Wystarczyło tylko, by inne klasy po niej dziedziczyły, a zyskałyby oferowane przez nią korzyści. Gdy w PHP 5.4 wprowadzono obsługę cech, klasa `Nette\Object` została zastąpiona cechą `Nette\SmartObject`. Wyeliminowało to potrzebę dziedziczenia po wspólnym przodku. Co więcej, cecha mogła być używana w klasach, które już dziedziczyły po innej klasie. Ostateczny koniec `Nette\Object` nastąpił wraz z wydaniem PHP 7.2, które zabroniło klasom nazywania się `Object`.

W miarę rozwoju PHP, jego model obiektowy i możliwości językowe ulegały poprawie. Różne funkcje klasy `SmartObject` stały się zbędne. Od wydania PHP 8.2 pozostała tylko jedna funkcja nieobsługiwana bezpośrednio w PHP: możliwość korzystania z tak zwanych [właściwości |#Properties, getters, and setters].

Jakie funkcje oferowały `Nette\Object` i, rozszerzając, `Nette\SmartObject`? Oto przegląd. (W przykładach użyto klasy `Nette\Object`, ale większość funkcji dotyczy również cechy `Nette\SmartObject` ).


Niespójne błędy .[#toc-nekonzistentni-chyby]
--------------------------------------------
PHP zachowywał się niespójnie podczas dostępu do niezadeklarowanych członków. Stan w momencie wejścia na stronę `Nette\Object` był następujący:

```php
echo $obj->undeclared; // E_NOTICE, później E_WARNING
$obj->undeclared = 1; // przechodzi cicho bez zgłaszania
$obj->unknownMethod(); // Błąd fatalny (nie do wychwycenia przez try/catch)
```

Fatal error zakończył działanie aplikacji bez możliwości reakcji. Ciche pisanie do nieistniejących członków bez ostrzeżenia mogło prowadzić do poważnych błędów trudnych do wykrycia. `Nette\Object` Wszystkie te przypadki zostały złapane i wyjątek rzucony przez `MemberAccessException`.

```php
echo $obj->undeclared;   // vyhodí Nette\MemberAccessException
$obj->undeclared = 1;    // vyhodí Nette\MemberAccessException
$obj->unknownMethod();   // vyhodí Nette\MemberAccessException
```
Od PHP 7.0, PHP nie powoduje już nieśledzonych błędów fatalnych, a dostęp do niezadeklarowanych członków jest błędem od PHP 8.2.


Miałeś na myśli? .[#toc-did-you-mean]
-------------------------------------
Jeśli został rzucony błąd `Nette\MemberAccessException`, być może z powodu literówki przy dostępie do zmiennej obiektu lub wywołaniu metody, `Nette\Object` próbował w komunikacie o błędzie dać podpowiedź, jak naprawić błąd, w postaci ikonicznego dodatku "czy miałeś na myśli?".

```php
class Foo extends Nette\Object
{
	public static function from($var)
	{
	}
}

$foo = Foo::form($var);
// vyhodí Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
```

Podczas gdy dzisiejszy PHP nie ma funkcji "czy miałeś na myśli?", ta fraza może być dodawana do błędów przez [Tracy |tracy:]. Może nawet [automatycznie korygować takie błędy |tracy:open-files-in-ide#toc-demos].


Metody rozszerzania .[#toc-extension-methods]
---------------------------------------------
Zainspirowany metodami rozszerzającymi z języka C#. Dawały one możliwość dodawania nowych metod do istniejących klas. Na przykład możesz dodać metodę `addDateTime()` do formularza, aby dodać własny DateTimePicker.

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

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

Metody rozszerzania okazały się niepraktyczne, ponieważ ich nazwy nie sugerowały redaktorów, zamiast tego informowały, że dana metoda nie istnieje. Dlatego też zaprzestano ich wspierania.


Uzyskanie nazwy klasy:
----------------------

```php
$class = $obj->getClass(); // używając Nette.
$class = $obj::class; // od PHP 8.0
```


Dostęp do refleksji i adnotacji
-------------------------------

`Nette\Object` zaoferował dostęp do refleksji i adnotacji za pomocą metod `getReflection()` i `getAnnotation()`:

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

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // vrátí 'John Doe'
```

Od PHP 8.0 możliwy jest dostęp do metainformacji w postaci atrybutów:

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

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


Metoda gettery
--------------

`Nette\Object` oferował elegancki sposób przekazywania metod tak, jakby były one zmiennymi:

```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 PHP 8.1 można używać tzw.  składni wywoływalnej pierwszej klasy:https://www.php.net/manual/en/functions.first_class_callable_syntax:

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


Wydarzenia
----------

`Nette\Object` zaoferował cukier syntaktyczny do wywołania [zdarzenia |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;
	}
}
```

Kod `$this->onChange($this, $radius)` jest równoważny z następującym:

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

Dla jasności, zalecamy unikanie metody magicznej `$this->onChange()`. Praktycznym substytutem jest funkcja [Nette\Utils\Arrays::invoke |arrays#invoke]:

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

SmartObject

SmartObject poprawiał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Wciąż jednak dodaje wsparcie dla property.

Instalacja:

composer require nette/utils

Właściwości, gettery i settery

W nowoczesnych językach obiektowych (np. C#, Python, Ruby, JavaScript) termin property odnosi się do specjalnych członków klas, które wyglądają jak zmienne, ale w rzeczywistości są reprezentowane przez metody. Kiedy wartość tej „zmiennej“ jest przypisywana lub odczytywana, wywoływana jest odpowiednia metoda (zwana getterem lub setterem). Jest to bardzo przydatna rzecz, daje nam pełną kontrolę nad dostępem do zmiennych. Możemy zatwierdzić dane wejściowe lub wygenerować wyniki tylko wtedy, gdy właściwość zostanie odczytana.

Właściwości PHP nie są obsługiwane, ale traita Nette\SmartObject może je imitować. Jak to zrobić?

  • Dodaj adnotację do klasy w postaci @property <type> $xyz
  • Utwórz getter o nazwie getXyz() lub isXyz(), setter o nazwie setXyz()
  • Getter i setter muszą być public lub protected i są opcjonalne, więc może być właściwość read-only lub write-only.

Wykorzystamy własność dla klasy Circle, aby zapewnić, że do zmiennej $radius wstawiane są tylko liczby nieujemne. Zamień public $radius na własność:

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

	private float $radius = 0.0; // není publiczne!

	// getter pro właściwość $radius
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// setter pro właściwość $radius
	protected function setRadius(float $radius): void
	{
		// hodnotu před uložením sanitizujeme
		$this->radius = max(0.0, $radius);
	}

	// getter pro właściwość $visible
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10;  // ve skutečnosti volá setRadius(10)
echo $circle->radius;  // volá getRadius()
echo $circle->visible; // volá isVisible()

Właściwości to przede wszystkim cukier syntaktyczny, który ma uczynić życie programisty słodszym poprzez uproszczenie kodu. Jeśli ich nie chcesz, nie musisz z nich korzystać.

Rzut oka na historię

SmartObject udoskonalał zachowanie obiektów na wiele sposobów, ale dzisiejszy PHP zawiera już większość tych ulepszeń natywnie. Poniższy tekst jest nostalgicznym spojrzeniem wstecz na historię, przypominającym nam, jak rzeczy ewoluowały.

Od samego początku model obiektowy PHP cierpiał na niezliczoną ilość poważnych niedociągnięć i braków. Doprowadziło to do stworzenia klasy Nette\Object (w 2007 roku), która miała na celu naprawienie tych problemów i zwiększenie komfortu korzystania z PHP. Wystarczyło tylko, by inne klasy po niej dziedziczyły, a zyskałyby oferowane przez nią korzyści. Gdy w PHP 5.4 wprowadzono obsługę cech, klasa Nette\Object została zastąpiona cechą Nette\SmartObject. Wyeliminowało to potrzebę dziedziczenia po wspólnym przodku. Co więcej, cecha mogła być używana w klasach, które już dziedziczyły po innej klasie. Ostateczny koniec Nette\Object nastąpił wraz z wydaniem PHP 7.2, które zabroniło klasom nazywania się Object.

W miarę rozwoju PHP, jego model obiektowy i możliwości językowe ulegały poprawie. Różne funkcje klasy SmartObject stały się zbędne. Od wydania PHP 8.2 pozostała tylko jedna funkcja nieobsługiwana bezpośrednio w PHP: możliwość korzystania z tak zwanych właściwości.

Jakie funkcje oferowały Nette\Object i, rozszerzając, Nette\SmartObject? Oto przegląd. (W przykładach użyto klasy Nette\Object, ale większość funkcji dotyczy również cechy Nette\SmartObject ).

Niespójne błędy

PHP zachowywał się niespójnie podczas dostępu do niezadeklarowanych członków. Stan w momencie wejścia na stronę Nette\Object był następujący:

echo $obj->undeclared; // E_NOTICE, później E_WARNING
$obj->undeclared = 1; // przechodzi cicho bez zgłaszania
$obj->unknownMethod(); // Błąd fatalny (nie do wychwycenia przez try/catch)

Fatal error zakończył działanie aplikacji bez możliwości reakcji. Ciche pisanie do nieistniejących członków bez ostrzeżenia mogło prowadzić do poważnych błędów trudnych do wykrycia. Nette\Object Wszystkie te przypadki zostały złapane i wyjątek rzucony przez MemberAccessException.

echo $obj->undeclared;   // vyhodí Nette\MemberAccessException
$obj->undeclared = 1;    // vyhodí Nette\MemberAccessException
$obj->unknownMethod();   // vyhodí Nette\MemberAccessException

Od PHP 7.0, PHP nie powoduje już nieśledzonych błędów fatalnych, a dostęp do niezadeklarowanych członków jest błędem od PHP 8.2.

Miałeś na myśli?

Jeśli został rzucony błąd Nette\MemberAccessException, być może z powodu literówki przy dostępie do zmiennej obiektu lub wywołaniu metody, Nette\Object próbował w komunikacie o błędzie dać podpowiedź, jak naprawić błąd, w postaci ikonicznego dodatku „czy miałeś na myśli?“.

class Foo extends Nette\Object
{
	public static function from($var)
	{
	}
}

$foo = Foo::form($var);
// vyhodí Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"

Podczas gdy dzisiejszy PHP nie ma funkcji „czy miałeś na myśli?“, ta fraza może być dodawana do błędów przez Tracy. Może nawet automatycznie korygować takie błędy.

Metody rozszerzania

Zainspirowany metodami rozszerzającymi z języka C#. Dawały one możliwość dodawania nowych metod do istniejących klas. Na przykład możesz dodać metodę addDateTime() do formularza, aby dodać własny DateTimePicker.

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

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

Metody rozszerzania okazały się niepraktyczne, ponieważ ich nazwy nie sugerowały redaktorów, zamiast tego informowały, że dana metoda nie istnieje. Dlatego też zaprzestano ich wspierania.

Uzyskanie nazwy klasy:

$class = $obj->getClass(); // używając Nette.
$class = $obj::class; // od PHP 8.0

Dostęp do refleksji i adnotacji

Nette\Object zaoferował dostęp do refleksji i adnotacji za pomocą metod getReflection() i getAnnotation():

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

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // vrátí 'John Doe'

Od PHP 8.0 możliwy jest dostęp do metainformacji w postaci atrybutów:

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

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

Metoda gettery

Nette\Object oferował elegancki sposób przekazywania metod tak, jakby były one zmiennymi:

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 PHP 8.1 można używać tzw. składni wywoływalnej pierwszej klasy:https://www.php.net/…lable_syntax:

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

Wydarzenia

Nette\Object zaoferował cukier syntaktyczny do wywołania zdarzenia:

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

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

Kod $this->onChange($this, $radius) jest równoważny z następującym:

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

Dla jasności, zalecamy unikanie metody magicznej $this->onChange(). Praktycznym substytutem jest funkcja Nette\Utils\Arrays::invoke:

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