/
smartobject.texy
236 lines (171 loc) · 12.6 KB
/
smartobject.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
SmartObject
***********
.[perex]
SmartObject поправяше поведението на обектите по много начини, но днешният PHP вече включва повечето от тези подобрения. Въпреки това той все още добавя поддръжка за *собственост*.
Настройка:
```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-a-glimpse-into-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\SmartObject`? Ето един преглед. (В примерите е използван класът `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:]. Тя дори може да [коригира такива грешки автоматично |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);
```