Nette Documentation Preview

syntax
SmartObject и StaticClass
*************************

.[perex]
SmartObject добавя поддръжка на *свойства* към класовете на PHP. StaticClass се използва за обозначаване на статични класове.


Настройка:

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


Свойства, getteri и setteri .[#toc-properties-getters-and-setters]
==================================================================

В съвременните обектно-ориентирани езици (напр. C#, Python, Ruby, JavaScript) терминът *собственост* се отнася до [специални членове на класа |https://en.wikipedia.org/wiki/Property_(programming)], които изглеждат като променливи, но всъщност са представени като методи. Когато се присвоява или чете стойността на такава "променлива", се извиква съответният метод (наречен getter или setter). Това е много удобно, тъй като ни дава пълен контрол върху достъпа до променливите. Можем да проверяваме входни данни или да генерираме резултати само когато свойството е прочетено.

PHP свойствата не се поддържат, но чертите `Nette\SmartObject` могат да ги симулират. Как да го използвате?

- Добавяне на анотация към класа под формата на `@property <type> $xyz`
- Създайте getter с име `getXyz()` или `isXyz()`, setter с име `setXyz()`
- Getter и setter трябва да са *публични* или *защитени* и незадължителни, така че може да има свойство *само за четене* или *само за писане*.

Ще използваме свойството за класа Circle, за да гарантираме, че в променливата `$radius` се поставят само неотрицателни числа. Заменете `public $radius` с имота:

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

	private float $radius = 0.0; // не е публичен

	// getter за свойството $radius
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// setter за свойството $radius
	protected function setRadius(float $radius): void
	{
		// обработване на стойността, преди да я запишем
		$this->radius = max(0.0, $radius);
	}

	// getter за свойството $visible
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10; // всъщност се извиква setRadius(10)
echo $circle->radius; // извиква getRadius()
echo $circle->visible; // извиква isVisible()
```

Свойствата са преди всичко "синтактична захар" и са предназначени да направят живота на програмиста по-сладък, като опростят кода. Ако нямате нужда от тях, не е нужно да ги използвате.


Статични класове .[#toc-static-classes]
=======================================

Статичните класове, т.е. класовете, които не са предназначени да бъдат инстанцирани, могат да бъдат маркирани с `Nette\StaticClass`:

```php
class Strings
{
	use Nette\StaticClass;
}
```

Когато се опитате да създадете инстанция, се появява изключение `Error`, което показва, че класът е статичен.


Поглед към историята .[#toc-a-look-into-the-history]
====================================================

SmartObject е използван за подобряване и коригиране на поведението на класовете по много начини, но развитието на PHP направи повечето от първоначалните функции излишни. Затова по-долу ще разгледаме историята на развитието на тези функции.

От самото начало обектният модел на PHP страдаше от някои сериозни недостатъци и неефективност. Това доведе до създаването на класа `Nette\Object` (през 2007 г.), който се опита да коригира тези проблеми и да подобри работата на потребителите на PHP. Това беше достатъчно, за да могат други класове да го наследят и да се възползват от предимствата му. Когато в PHP 5.4 беше въведена поддръжка на черти, класът `Nette\Object` беше заменен с `Nette\SmartObject`. По този начин вече не е било необходимо да се наследява от общ прародител. Освен това чертата може да се използва в класове, които вече са наследени от друг клас. Окончателният край на `Nette\Object` настъпи с излизането на PHP 7.2, който забрани именуването на класове `Object`.

С развитието на PHP обектният модел и функциите на езика се усъвършенстваха. Някои характеристики на класа `SmartObject` станаха излишни. След излизането на PHP 8.2 единствената функция, която все още не се поддържа директно в PHP, е възможността за използване на т.нар. [свойства |#Properties, getters and setters].

Какви функции предлагат `Nette\Object` и `Nette\Object`? Ето един преглед. (В примерите е използван класът `Nette\Object`, но повечето от свойствата се отнасят и за `Nette\SmartObject` ).


Непоследователни грешки .[#toc-inconsistent-errors]
---------------------------------------------------
PHP имаше непоследователно поведение при достъп до недекларирани членове. Към момента на публикуване на `Nette\Object` статусът е следният:

```php
echo $obj->undeclared; // E_NOTICE, по-късно E_WARNING
$obj->undeclared = 1; // преминава безшумно, без да съобщава
$obj->unknownMethod(); // Фатална грешка (която не може да бъде уловена чрез try/catch)
```

Фатална грешка ще прекрати приложението без възможност за реакция. Тихото записване на несъществуващи членове без предупреждение можеше да доведе до сериозни грешки, които трудно се откриваха. `Nette\Object` Всички тези случаи бяха уловени и беше хвърлено изключение на адрес `MemberAccessException`.

```php
echo $obj->undeclared;   // throw Nette\MemberAccessException
$obj->undeclared = 1;    // throw Nette\MemberAccessException
$obj->unknownMethod();   // throw Nette\MemberAccessException
```
От PHP 7.0 PHP вече не причинява фатални грешки, които не могат да бъдат прихванати, а достъпът до необявени членове е грешка от PHP 8.2 насам.


Какво имаш предвид? .[#toc-did-you-mean]
----------------------------------------
Ако се появи грешка в `Nette\MemberAccessException`, вероятно поради печатна грешка при достъп до променлива на обект или извикване на метод, `Nette\Object` се опитва да подскаже в съобщението за грешка как да се поправи грешката под формата на емблематичното допълнение "Искахте ли да кажете?".

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

Съвременният PHP може и да не разполага с формата "Искахте ли да кажете?", но [Трейси |tracy:] добавя тази добавка към грешките. Той дори може сам да [коригира |tracy:open-files-in-ide#toc-demos] такива грешки.


Методи за разширение .[#toc-extension-methods]
----------------------------------------------
Вдъхновено от методите за разширение от C#. Те ви позволяват да добавяте нови методи към съществуващи класове. Например можете да добавите метод `addDateTime()` към формуляр, за да добавите свой собствен DateTimePicker.

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

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

Методите за разширение се оказаха непрактични, тъй като имената им не се попълваха автоматично от редакторите, а вместо това те съобщаваха, че методът не съществува. Поради това тяхната подкрепа беше преустановена.


Получаване на името на класа .[#toc-getting-the-class-name]
-----------------------------------------------------------

```php
$class = $obj->getClass(); // използване на Nette\Object
$class = $obj::class; // от PHP 8.0
```


Достъп до рефлексии и анотации .[#toc-access-to-reflection-and-annotations]
---------------------------------------------------------------------------

`Nette\Object` Достъп до размишленията и анотациите чрез методите `getReflection()` и `getAnnotation()`:

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

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // връща 'John Doe
```

От версия 8.0 на PHP вече е възможен достъп до метаинформация под формата на атрибути:

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

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


Получатели на методи .[#toc-method-getters]
-------------------------------------------

`Nette\Object` предлагат елегантен начин за работа с методите, сякаш са променливи:

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

От версия 8.1 на PHP можете да използвате така наречения "синтаксис на извикване на метод от първи клас":https://www.php.net/manual/en/functions.first_class_callable_syntax:

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


Събития .[#toc-events]
----------------------

`Nette\Object` предлага синтактична захар за задействане на [събитие |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
	}
}
```

Кодът `$this->onChange($this, $radius)` е еквивалентен на следното:

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

За по-голяма яснота препоръчваме да избягвате магическия метод `$this->onChange()`. Добър заместител би бил [Nette\Utils\Arrays::invoke: |arrays#invoke]

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

SmartObject и StaticClass

SmartObject добавя поддръжка на свойства към класовете на PHP. StaticClass се използва за обозначаване на статични класове.

Настройка:

composer require nette/utils

Свойства, getteri и setteri

В съвременните обектно-ориентирани езици (напр. C#, Python, Ruby, JavaScript) терминът собственост се отнася до специални членове на класа, които изглеждат като променливи, но всъщност са представени като методи. Когато се присвоява или чете стойността на такава „променлива“, се извиква съответният метод (наречен getter или setter). Това е много удобно, тъй като ни дава пълен контрол върху достъпа до променливите. Можем да проверяваме входни данни или да генерираме резултати само когато свойството е прочетено.

PHP свойствата не се поддържат, но чертите Nette\SmartObject могат да ги симулират. Как да го използвате?

  • Добавяне на анотация към класа под формата на @property <type> $xyz
  • Създайте getter с име getXyz() или isXyz(), setter с име setXyz()
  • Getter и setter трябва да са публични или защитени и незадължителни, така че може да има свойство само за четене или само за писане.

Ще използваме свойството за класа Circle, за да гарантираме, че в променливата $radius се поставят само неотрицателни числа. Заменете public $radius с имота:

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

	private float $radius = 0.0; // не е публичен

	// getter за свойството $radius
	protected function getRadius(): float
	{
		return $this->radius;
	}

	// setter за свойството $radius
	protected function setRadius(float $radius): void
	{
		// обработване на стойността, преди да я запишем
		$this->radius = max(0.0, $radius);
	}

	// getter за свойството $visible
	protected function isVisible(): bool
	{
		return $this->radius > 0;
	}
}

$circle = new Circle;
$circle->radius = 10; // всъщност се извиква setRadius(10)
echo $circle->radius; // извиква getRadius()
echo $circle->visible; // извиква isVisible()

Свойствата са преди всичко „синтактична захар“ и са предназначени да направят живота на програмиста по-сладък, като опростят кода. Ако нямате нужда от тях, не е нужно да ги използвате.

Статични класове

Статичните класове, т.е. класовете, които не са предназначени да бъдат инстанцирани, могат да бъдат маркирани с Nette\StaticClass:

class Strings
{
	use Nette\StaticClass;
}

Когато се опитате да създадете инстанция, се появява изключение Error, което показва, че класът е статичен.

Поглед към историята

SmartObject е използван за подобряване и коригиране на поведението на класовете по много начини, но развитието на PHP направи повечето от първоначалните функции излишни. Затова по-долу ще разгледаме историята на развитието на тези функции.

От самото начало обектният модел на PHP страдаше от някои сериозни недостатъци и неефективност. Това доведе до създаването на класа Nette\Object (през 2007 г.), който се опита да коригира тези проблеми и да подобри работата на потребителите на PHP. Това беше достатъчно, за да могат други класове да го наследят и да се възползват от предимствата му. Когато в PHP 5.4 беше въведена поддръжка на черти, класът Nette\Object беше заменен с Nette\SmartObject. По този начин вече не е било необходимо да се наследява от общ прародител. Освен това чертата може да се използва в класове, които вече са наследени от друг клас. Окончателният край на Nette\Object настъпи с излизането на PHP 7.2, който забрани именуването на класове Object.

С развитието на PHP обектният модел и функциите на езика се усъвършенстваха. Някои характеристики на класа SmartObject станаха излишни. След излизането на PHP 8.2 единствената функция, която все още не се поддържа директно в PHP, е възможността за използване на т.нар. свойства.

Какви функции предлагат Nette\Object и Nette\Object? Ето един преглед. (В примерите е използван класът Nette\Object, но повечето от свойствата се отнасят и за Nette\SmartObject ).

Непоследователни грешки

PHP имаше непоследователно поведение при достъп до недекларирани членове. Към момента на публикуване на Nette\Object статусът е следният:

echo $obj->undeclared; // E_NOTICE, по-късно E_WARNING
$obj->undeclared = 1; // преминава безшумно, без да съобщава
$obj->unknownMethod(); // Фатална грешка (която не може да бъде уловена чрез try/catch)

Фатална грешка ще прекрати приложението без възможност за реакция. Тихото записване на несъществуващи членове без предупреждение можеше да доведе до сериозни грешки, които трудно се откриваха. Nette\Object Всички тези случаи бяха уловени и беше хвърлено изключение на адрес MemberAccessException.

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

От PHP 7.0 PHP вече не причинява фатални грешки, които не могат да бъдат прихванати, а достъпът до необявени членове е грешка от PHP 8.2 насам.

Какво имаш предвид?

Ако се появи грешка в Nette\MemberAccessException, вероятно поради печатна грешка при достъп до променлива на обект или извикване на метод, Nette\Object се опитва да подскаже в съобщението за грешка как да се поправи грешката под формата на емблематичното допълнение „Искахте ли да кажете?“.

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

Съвременният PHP може и да не разполага с формата „Искахте ли да кажете?“, но Трейси добавя тази добавка към грешките. Той дори може сам да коригира такива грешки.

Методи за разширение

Вдъхновено от методите за разширение от C#. Те ви позволяват да добавяте нови методи към съществуващи класове. Например можете да добавите метод addDateTime() към формуляр, за да добавите свой собствен DateTimePicker.

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

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

Методите за разширение се оказаха непрактични, тъй като имената им не се попълваха автоматично от редакторите, а вместо това те съобщаваха, че методът не съществува. Поради това тяхната подкрепа беше преустановена.

Получаване на името на класа

$class = $obj->getClass(); // използване на Nette\Object
$class = $obj::class; // от PHP 8.0

Достъп до рефлексии и анотации

Nette\Object Достъп до размишленията и анотациите чрез методите getReflection() и getAnnotation():

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

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // връща 'John Doe

От версия 8.0 на PHP вече е възможен достъп до метаинформация под формата на атрибути:

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

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

Получатели на методи

Nette\Object предлагат елегантен начин за работа с методите, сякаш са променливи:

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

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

От версия 8.1 на PHP можете да използвате така наречения синтаксис на извикване на метод от първи клас:

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

Събития

Nette\Object предлага синтактична захар за задействане на събитие:

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

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

Кодът $this->onChange($this, $radius) е еквивалентен на следното:

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

За по-голяма яснота препоръчваме да избягвате магическия метод $this->onChange(). Добър заместител би бил Nette\Utils\Arrays::invoke:

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