1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* WebHemi. |
4
|
|
|
* |
5
|
|
|
* PHP version 5.6 |
6
|
|
|
* |
7
|
|
|
* @copyright 2012 - 2016 Gixx-web (http://www.gixx-web.com) |
8
|
|
|
* @license https://opensource.org/licenses/MIT The MIT License (MIT) |
9
|
|
|
* |
10
|
|
|
* @link http://www.gixx-web.com |
11
|
|
|
*/ |
12
|
|
|
namespace WebHemi\Form\Element\Web; |
13
|
|
|
|
14
|
|
|
use Exception; |
15
|
|
|
use InvalidArgumentException; |
16
|
|
|
use Iterator; |
17
|
|
|
use RuntimeException; |
18
|
|
|
use WebHemi\Form\Element\FormElementInterface; |
19
|
|
|
use WebHemi\Form\Element\NestedElementInterface; |
20
|
|
|
use WebHemi\Form\Traits\CamelCaseToUnderScoreTrait; |
21
|
|
|
use WebHemi\Form\Traits\IteratorTrait; |
22
|
|
|
use WebHemi\Validator\ValidatorInterface; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Class AbstractElement |
26
|
|
|
*/ |
27
|
|
|
abstract class AbstractElement implements FormElementInterface, Iterator |
28
|
|
|
{ |
29
|
|
|
/** @var string */ |
30
|
|
|
protected $type = ''; |
31
|
|
|
/** @var int */ |
32
|
|
|
protected static $tabIndex = 1; |
33
|
|
|
|
34
|
|
|
/** @var string */ |
35
|
|
|
protected $name; |
36
|
|
|
/** @var string */ |
37
|
|
|
protected $label; |
38
|
|
|
/** @var mixed */ |
39
|
|
|
protected $value; |
40
|
|
|
/** @var array */ |
41
|
|
|
protected $attributes = []; |
42
|
|
|
/** @var array<ValidatorInterface> */ |
43
|
|
|
protected $validators = []; |
44
|
|
|
/** @var array */ |
45
|
|
|
protected $errors = []; |
46
|
|
|
/** @var FormElementInterface */ |
47
|
|
|
protected $parentNode; |
48
|
|
|
/** @var array */ |
49
|
|
|
protected $mandatoryParentTypes = []; |
50
|
|
|
|
51
|
|
|
// The implementation of the Iterator interface. |
52
|
|
|
use IteratorTrait; |
53
|
|
|
// CamelCase to under_score converter |
54
|
|
|
use CamelCaseToUnderScoreTrait; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* AbstractFormElement constructor. |
58
|
|
|
* |
59
|
|
|
* @param string $name |
60
|
|
|
* @param string $label |
61
|
|
|
* @param mixed $value |
62
|
|
|
*/ |
63
|
21 |
|
public function __construct($name = '', $label = '', $value = null) |
64
|
|
|
{ |
65
|
21 |
|
$this->name = preg_replace('/[^a-z0-9]/', '_', strtolower($name)); |
66
|
21 |
|
$this->label = $label; |
67
|
21 |
|
$this->value = $value; |
68
|
21 |
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns the element type. |
72
|
|
|
* |
73
|
|
|
* @throws Exception |
74
|
|
|
* @return string |
75
|
|
|
*/ |
76
|
2 |
|
final public function getType() |
77
|
|
|
{ |
78
|
2 |
|
if (empty($this->type)) { |
79
|
1 |
|
throw new Exception('You must specify the element type in the $type class property.'); |
80
|
|
|
} |
81
|
|
|
|
82
|
2 |
|
return $this->type; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Sets element name. The implementation should decide if it is allowed after init. |
87
|
|
|
* |
88
|
|
|
* @param string $name |
89
|
|
|
* @return AbstractElement |
90
|
|
|
*/ |
91
|
9 |
|
public function setName($name) |
92
|
|
|
{ |
93
|
9 |
|
$this->name = $name; |
94
|
|
|
|
95
|
9 |
|
return $this; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Returns the element name. If parameter is TRUE, then the method should include all the parents' names as well. |
100
|
|
|
* |
101
|
|
|
* @param boolean $getFulNodeName |
102
|
|
|
* @return string |
103
|
|
|
*/ |
104
|
12 |
|
public function getName($getFulNodeName = true) |
105
|
|
|
{ |
106
|
12 |
|
$name = $this->name; |
107
|
|
|
|
108
|
12 |
|
if ($getFulNodeName) { |
109
|
7 |
|
if ($this->parentNode instanceof FormElementInterface) { |
110
|
1 |
|
$name = $this->parentNode->getName().'['.$this->name.']'; |
111
|
1 |
|
} |
112
|
7 |
|
} |
113
|
|
|
|
114
|
12 |
|
return $name; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Sets element label. |
119
|
|
|
* |
120
|
|
|
* @param string $label |
121
|
|
|
* @return AbstractElement |
122
|
|
|
*/ |
123
|
1 |
|
public function setLabel($label) |
124
|
|
|
{ |
125
|
1 |
|
$this->label = $label; |
126
|
|
|
|
127
|
1 |
|
return $this; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Returns the element label. |
132
|
|
|
* |
133
|
|
|
* @return string |
134
|
|
|
*/ |
135
|
1 |
|
public function getLabel() |
136
|
|
|
{ |
137
|
1 |
|
return $this->label; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Sets element value. |
142
|
|
|
* |
143
|
|
|
* @param mixed $value |
144
|
|
|
* @return AbstractElement |
145
|
|
|
*/ |
146
|
8 |
|
public function setValue($value) |
147
|
|
|
{ |
148
|
8 |
|
$this->value = $value; |
149
|
|
|
|
150
|
8 |
|
return $this; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Returns element value. |
155
|
|
|
* |
156
|
|
|
* @return mixed |
157
|
|
|
*/ |
158
|
1 |
|
public function getValue() |
159
|
|
|
{ |
160
|
1 |
|
return $this->value; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Gets element Id. |
165
|
|
|
* |
166
|
|
|
* @return string |
167
|
|
|
*/ |
168
|
1 |
|
public function getId() |
169
|
|
|
{ |
170
|
1 |
|
$name = $this->getName(); |
171
|
1 |
|
$md5Match = []; |
172
|
|
|
|
173
|
|
|
// Rip off the unique form prefix to make possible to work with fixed CSS id selectors. |
174
|
1 |
|
if (preg_match('/^.+(?P<md5>\_[a-f0-9]{32})($|\_.*$)/', $name, $md5Match)) { |
175
|
1 |
|
$name = str_replace($md5Match['md5'], '', $name); |
176
|
1 |
|
} |
177
|
|
|
|
178
|
1 |
|
$elementId = 'id_'.trim(preg_replace('/[^a-zA-Z0-9]/', '_', $name), '_'); |
179
|
1 |
|
$elementId = $this->camelCaseToUnderscore($elementId); |
180
|
|
|
|
181
|
1 |
|
return str_replace('__', '_', $elementId); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Sets multiple attributes. |
186
|
|
|
* |
187
|
|
|
* @param array $attributes |
188
|
|
|
* @return AbstractElement |
189
|
|
|
*/ |
190
|
11 |
|
public function setAttributes(array $attributes) |
191
|
|
|
{ |
192
|
11 |
|
$this->attributes = []; |
193
|
|
|
|
194
|
11 |
|
foreach ($attributes as $key => $value) { |
195
|
11 |
|
$this->setAttribute($key, $value); |
196
|
10 |
|
} |
197
|
|
|
|
198
|
10 |
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Sets element attribute. |
203
|
|
|
* |
204
|
|
|
* @param string $key |
205
|
|
|
* @param mixed $value |
206
|
|
|
* @throws InvalidArgumentException |
207
|
|
|
* @return AbstractElement |
208
|
|
|
*/ |
209
|
11 |
|
protected function setAttribute($key, $value) |
210
|
|
|
{ |
211
|
11 |
|
if (!is_scalar($value)) { |
212
|
1 |
|
throw new InvalidArgumentException('Element attribute can hold scalar data only.'); |
213
|
|
|
} |
214
|
|
|
|
215
|
10 |
|
$this->attributes[$key] = $value; |
216
|
|
|
|
217
|
10 |
|
return $this; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Gets all the attributes. |
222
|
|
|
* |
223
|
|
|
* @return array |
224
|
|
|
*/ |
225
|
6 |
|
public function getAttributes() |
226
|
|
|
{ |
227
|
6 |
|
return $this->attributes; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Gets element attribute. |
232
|
|
|
* |
233
|
|
|
* @param string $name |
234
|
|
|
* @return mixed |
235
|
|
|
*/ |
236
|
2 |
|
public function getAttribute($name) |
237
|
|
|
{ |
238
|
2 |
|
if (!isset($this->attributes[$name])) { |
239
|
2 |
|
throw new InvalidArgumentException(sprintf('Invalid attribute: `%s`', $name)); |
240
|
|
|
} |
241
|
|
|
|
242
|
2 |
|
return $this->attributes[$name]; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Sets and increments the tabulator index globally. This method should be used only on visible elements. |
247
|
|
|
* |
248
|
|
|
* @param bool $reset |
249
|
|
|
* @return AbstractElement |
250
|
|
|
*/ |
251
|
20 |
|
public function setTabIndex($reset = false) |
252
|
|
|
{ |
253
|
20 |
|
if ($reset) { |
254
|
1 |
|
self::$tabIndex = 1; |
255
|
1 |
|
} |
256
|
|
|
|
257
|
20 |
|
$this->attributes['tabindex'] = self::$tabIndex++; |
258
|
|
|
|
259
|
20 |
|
return $this; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Sets the element errors. Usually the validator should set it, but it is allowed to set from outside too. |
264
|
|
|
* |
265
|
|
|
* @param array $errors |
266
|
|
|
* @return AbstractElement |
267
|
|
|
*/ |
268
|
1 |
|
public function setErrors(array $errors) |
269
|
|
|
{ |
270
|
1 |
|
$this->errors = $errors; |
271
|
|
|
|
272
|
1 |
|
return $this; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Checks if there are error messages set. |
277
|
|
|
* |
278
|
|
|
* @return boolean |
279
|
|
|
*/ |
280
|
1 |
|
public function hasErrors() |
281
|
|
|
{ |
282
|
1 |
|
return !empty($this->errors); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Gets validation errors. |
287
|
|
|
* |
288
|
|
|
* @return array |
289
|
|
|
*/ |
290
|
2 |
|
public function getErrors() |
291
|
|
|
{ |
292
|
2 |
|
return $this->errors; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Sets the element validators. |
297
|
|
|
* |
298
|
|
|
* @param array<ValidatorInterface> $validators |
299
|
|
|
* @return FormElementInterface |
300
|
|
|
*/ |
301
|
1 |
|
public function setValidators(array $validators) |
302
|
|
|
{ |
303
|
1 |
|
$this->validators = []; |
304
|
|
|
|
305
|
1 |
|
foreach ($validators as $validator) { |
306
|
1 |
|
$this->addValidator($validator); |
307
|
1 |
|
} |
308
|
|
|
|
309
|
1 |
|
return $this; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Adds validator to the form. |
314
|
|
|
* |
315
|
|
|
* @param ValidatorInterface $validator |
316
|
|
|
* @return AbstractElement |
317
|
|
|
*/ |
318
|
1 |
|
protected function addValidator(ValidatorInterface $validator) |
319
|
|
|
{ |
320
|
1 |
|
$this->validators[] = $validator; |
321
|
|
|
|
322
|
1 |
|
return $this; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Gets the element validators. |
327
|
|
|
* |
328
|
|
|
* @return array<ValidatorInterface> |
329
|
|
|
*/ |
330
|
1 |
|
public function getValidators() |
331
|
|
|
{ |
332
|
1 |
|
return $this->validators; |
|
|
|
|
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Validates element value. |
337
|
|
|
* |
338
|
|
|
* @param bool $reValidate |
339
|
|
|
* @return bool |
340
|
|
|
*/ |
341
|
3 |
|
public function isValid($reValidate = false) |
342
|
|
|
{ |
343
|
3 |
|
if ($reValidate) { |
344
|
2 |
|
$this->errors = []; |
345
|
|
|
|
346
|
|
|
/** @var ValidatorInterface $validator */ |
347
|
2 |
|
foreach ($this->validators as $validator) { |
348
|
1 |
|
if (!$validator->validate($this->value)) { |
349
|
1 |
|
$this->errors[] = $validator->getError(); |
350
|
1 |
|
} |
351
|
2 |
|
} |
352
|
2 |
|
} |
353
|
|
|
|
354
|
3 |
|
return empty($this->errors); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* Sets the parent element. |
359
|
|
|
* |
360
|
|
|
* @param FormElementInterface $formElement |
361
|
|
|
* @throws RuntimeException |
362
|
|
|
* @return AbstractElement |
363
|
|
|
*/ |
364
|
10 |
|
public function setParentNode(FormElementInterface $formElement) |
365
|
|
|
{ |
366
|
10 |
|
if (!$formElement instanceof NestedElementInterface) { |
367
|
1 |
|
throw new RuntimeException( |
368
|
1 |
|
sprintf( |
369
|
1 |
|
'Cannot set `%s` as child element of `%s`.', |
370
|
1 |
|
$this->getType(), |
371
|
1 |
|
$formElement->getType() |
372
|
1 |
|
) |
373
|
1 |
|
); |
374
|
|
|
} |
375
|
|
|
|
376
|
10 |
|
$this->parentNode = $formElement; |
377
|
|
|
|
378
|
10 |
|
return $this; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Gets the parent element. |
383
|
|
|
* |
384
|
|
|
* @return FormElementInterface |
385
|
|
|
*/ |
386
|
1 |
|
public function getParentNode() |
387
|
|
|
{ |
388
|
1 |
|
return $this->parentNode; |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.