Completed
Push — master ( 71422b...5978c7 )
by Gabor
03:52
created

FormElement   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 420
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 39
lcom 2
cbo 1
dl 0
loc 420
ccs 0
cts 157
cp 0
rs 8.2857
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getTagName() 0 4 1
A setParentNode() 0 20 3
B getName() 0 17 5
A getLabel() 0 4 1
A setValue() 0 6 1
A getValue() 0 4 1
A setOptions() 0 9 2
A setOption() 0 15 2
A hasOptions() 0 4 1
A getOptions() 0 4 1
A addChildNode() 0 8 1
A getChildNodes() 0 4 1
A setAttribute() 0 14 3
A setAttributes() 0 8 2
A getAttribute() 0 8 2
A getAttributes() 0 4 1
A addValidator() 0 6 1
A isValid() 0 10 3
A current() 0 4 1
A next() 0 4 1
A key() 0 4 1
A valid() 0 6 2
A rewind() 0 4 1
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;
13
14
use Iterator;
15
use InvalidArgumentException;
16
use RuntimeException;
17
use WebHemi\Form\Validator\FormValidatorInterface;
18
19
/**
20
 * Class FormElement
21
 */
22
final class FormElement implements Iterator
23
{
24
    /** HTML5 form elements */
25
    const TAG_FORM = 'form';
26
    const TAG_INPUT_CHECKBOX = 'checkbox';
27
    const TAG_INPUT_COLOR = 'color';
28
    const TAG_INPUT_DATA = 'date';
29
    const TAG_INPUT_DATETIME = 'datetime';
30
    const TAG_INPUT_DATETIME_LOCAL = 'datetime-local';
31
    const TAG_INPUT_EMAIL = 'email';
32
    const TAG_INPUT_FILE = 'file';
33
    const TAG_INPUT_HIDDEN = 'hidden';
34
    const TAG_INPUT_IMAGE = 'image';
35
    const TAG_INPUT_MONTH = 'month';
36
    const TAG_INPUT_NUMBER = 'number';
37
    const TAG_INPUT_PASSWORD = 'password';
38
    const TAG_INPUT_RADIO = 'radio';
39
    const TAG_INPUT_RANGE = 'range';
40
    const TAG_INPUT_SEARCH = 'search';
41
    const TAG_INPUT_TEL = 'tel';
42
    const TAG_INPUT_TEXT = 'text';
43
    const TAG_INPUT_TIME = 'time';
44
    const TAG_INPUT_URL = 'url';
45
    const TAG_INPUT_WEEK = 'week';
46
    const TAG_TEXTAREA = 'textarea';
47
    const TAG_FIELDSET = 'fieldset';
48
    const TAG_LEGEND = 'legend';
49
    const TAG_LABEL = 'label';
50
    const TAG_BUTTON_SUBMIT = 'submit';
51
    const TAG_BUTTON_RESET = 'reset';
52
    const TAG_BUTTON = 'button';
53
    const TAG_DATALIST = 'datalist';
54
    const TAG_SELECT = 'select';
55
    const TAG_OPTION_GROUP = 'optgroup';
56
    const TAG_OPTION = 'option';
57
    const TAG_KEYGEN = 'keygen';
58
    const TAG_OUTPUT = 'output';
59
60
    /** @var int */
61
    protected static $tabIndex = 1;
62
    /** @var string */
63
    private $tagName;
64
    /** @var string */
65
    private $name;
66
    /** @var string */
67
    private $label;
68
    /** @var mixed */
69
    private $value;
70
    /** @var array */
71
    private $options = [];
72
    /** @var array */
73
    private $attributes;
74
    /** @var FormElement */
75
    private $parentNode;
76
    /** @var array<FormElement> */
77
    private $childNodes;
78
    /** @var array<FormValidatorInterface> */
79
    private $validators;
80
    /** @var array */
81
    private $mandatoryTagParents = [
82
        self::TAG_FORM => [],
83
        self::TAG_LEGEND => [
84
            self::TAG_FIELDSET
85
        ],
86
        self::TAG_OPTION => [
87
            self::TAG_DATALIST,
88
            self::TAG_OPTION_GROUP,
89
            self::TAG_SELECT
90
        ],
91
        self::TAG_OPTION_GROUP => [
92
            self::TAG_SELECT
93
        ],
94
    ];
95
    /** @var array */
96
    private $multiOptionTags = [
97
        self::TAG_SELECT,
98
        self::TAG_INPUT_RADIO,
99
        self::TAG_INPUT_CHECKBOX,
100
        self::TAG_DATALIST
101
    ];
102
103
    /**
104
     * FormElement constructor.
105
     *
106
     * @param string $tagName
107
     * @param string $name
108
     * @param string $label
109
     */
110
    public function __construct($tagName, $name, $label = '')
111
    {
112
        $this->tagName = $tagName;
113
        $this->name = $name;
114
        $this->label = $label;
115
        $this->attributes['tabindex'] = self::$tabIndex++;
116
    }
117
118
    /**
119
     * Returns the element tag name.
120
     *
121
     * @return string
122
     */
123
    public function getTagName()
124
    {
125
        return $this->tagName;
126
    }
127
128
    /**
129
     * Sets parent element name
130
     *
131
     * @param FormElement $formElement
132
     * @throws RuntimeException
133
     * @return FormElement
134
     */
135
    public function setParentNode(FormElement $formElement)
136
    {
137
        $parentTagName = $formElement->getTagName();
138
139
        if (isset($this->mandatoryTagParents[$this->tagName])
140
            && !in_array($parentTagName, $this->mandatoryTagParents[$this->tagName])
141
        ) {
142
            throw new RuntimeException(
143
                sprintf(
144
                    'Cannot set `%s` as child element of `%s`.',
145
                    $this->tagName,
146
                    $parentTagName
147
                )
148
            );
149
        }
150
151
        $this->parentNode = $formElement;
152
153
        return $this;
154
    }
155
156
    /**
157
     * Returns the element name.
158
     *
159
     * @return string
160
     */
161
    public function getName()
162
    {
163
        $name = $this->name;
164
165
        if (isset($this->parentNode)) {
166
            $name = $this->parentNode->getName() . '[' . $this->name . ']';
167
        }
168
169
        if (count($this->options) > 1
170
            && $this->tagName  == self::TAG_SELECT
171
            && !empty($this->attributes['multiple'])
172
        ) {
173
            $name .= '[]';
174
        }
175
176
        return $name;
177
    }
178
179
    /**
180
     * Returns the element label.
181
     *
182
     * @return string
183
     */
184
    public function getLabel()
185
    {
186
        return $this->label;
187
    }
188
189
    /**
190
     * Sets element value.
191
     *
192
     * @param mixed $value
193
     * @return FormElement
194
     */
195
    public function setValue($value)
196
    {
197
        $this->value = $value;
198
199
        return $this;
200
    }
201
202
    /**
203
     * Returns element value.
204
     *
205
     * @return mixed
206
     */
207
    public function getValue()
208
    {
209
        return $this->value;
210
    }
211
212
    /**
213
     * Set label-value options for the element.
214
     *
215
     * @param array $options
216
     * @throws RuntimeException
217
     * @return FormElement
218
     */
219
    public function setOptions(array $options)
220
    {
221
        foreach ($options as $option) {
222
            $checked = !empty($option['checked']);
223
            $this->setOption($option['label'], $option['value'], $checked);
224
        }
225
226
        return $this;
227
    }
228
229
    /**
230
     * Sets label-value option for the element.
231
     *
232
     * @param string  $label
233
     * @param string  $value
234
     * @param boolean $checked
235
     * @return FormElement
236
     */
237
    public function setOption($label, $value, $checked = false)
238
    {
239
        if (!in_array($this->tagName, $this->multiOptionTags)) {
240
            throw new RuntimeException(sprintf('Cannot set value options for `%s` element.', $this->tagName));
241
        }
242
243
        // The label should be unique.
244
        $this->options[$label] = [
245
            'label' => $label,
246
            'value' => $value,
247
            'checked' => $checked
248
        ];
249
250
        return $this;
251
    }
252
253
    /**
254
     * Checks if the element has value options.
255
     *
256
     * @return bool
257
     */
258
    public function hasOptions()
259
    {
260
        return !empty($this->options);
261
    }
262
263
    /**
264
     * Gets element value options.
265
     *
266
     * @return array
267
     */
268
    public function getOptions()
269
    {
270
        return $this->options;
271
    }
272
273
    /**
274
     * Set child node for the element.
275
     *
276
     * @param FormElement $childNode
277
     * @return FormElement
278
     */
279
    public function addChildNode(FormElement $childNode)
280
    {
281
        $childNode->setParentNode($this);
282
283
        $this->childNodes[] = $childNode;
284
285
        return $this;
286
    }
287
288
    /**
289
     * Gets the child nodes of the element.
290
     *
291
     * @return array<FormElement>
292
     */
293
    public function getChildNodes()
294
    {
295
        return $this->childNodes;
296
    }
297
298
    /**
299
     * Sets element attribute.
300
     *
301
     * @param string $key
302
     * @param string $value
303
     * @throws InvalidArgumentException
304
     * @return FormElement
305
     */
306
    public function setAttribute($key, $value)
307
    {
308
        if ($key == 'name') {
309
            throw new InvalidArgumentException('Cannot change element name after it has been initialized.');
310
        }
311
312
        if (!is_scalar($value)) {
313
            throw new InvalidArgumentException('Element attribute can hold scalar data only.');
314
        }
315
316
        $this->attributes[$key] = $value;
317
318
        return $this;
319
    }
320
321
    /**
322
     * Sets multiple attributes.
323
     *
324
     * @param array $attributes
325
     * @return FormElement
326
     */
327
    public function setAttributes(array $attributes)
328
    {
329
        foreach ($attributes as $key => $value) {
330
            $this->setAttribute($key, $value);
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * Gets element attribute.
338
     *
339
     * @param string $name
340
     * @return mixed
341
     */
342
    public function getAttribute($name)
343
    {
344
        if (!isset($this->attributes[$name])) {
345
            throw new RuntimeException(sprintf('Invalid attribute: `%s`', $name));
346
        }
347
348
        return $this->attributes[$name];
349
    }
350
351
    /**
352
     * Gets all the attributes.
353
     *
354
     * @return array
355
     */
356
    public function getAttributes()
357
    {
358
        return $this->attributes;
359
    }
360
361
    /**
362
     * Adds validator to the form.
363
     *
364
     * @param FormValidatorInterface $validator
365
     * @return FormElement
366
     */
367
    public function addValidator(FormValidatorInterface $validator)
368
    {
369
        $this->validators[] = $validator;
370
371
        return $this;
372
    }
373
374
    /**
375
     * Validates element value.
376
     *
377
     * @return bool
378
     */
379
    public function isValid()
380
    {
381
        foreach ($this->validators as $validator) {
382
            if (!$validator->validate($this->value)) {
383
                return false;
384
            }
385
        }
386
387
        return true;
388
    }
389
390
    /**
391
     * Return the current element.
392
     *
393
     * @return FormElement
394
     */
395
    final public function current()
0 ignored issues
show
Coding Style introduced by
Unnecessary FINAL modifier in FINAL class
Loading history...
396
    {
397
        return current($this->childNodes);
398
    }
399
400
    /**
401
     * Moves the pointer forward to next element.
402
     *
403
     * @return void
404
     */
405
    final public function next()
0 ignored issues
show
Coding Style introduced by
Unnecessary FINAL modifier in FINAL class
Loading history...
406
    {
407
        next($this->childNodes);
408
    }
409
410
    /**
411
     * Returns the key of the current element.
412
     *
413
     * @return mixed
414
     */
415
    final public function key()
0 ignored issues
show
Coding Style introduced by
Unnecessary FINAL modifier in FINAL class
Loading history...
416
    {
417
        return key($this->childNodes);
418
    }
419
420
    /**
421
     * Checks if current position is valid.
422
     *
423
     * @return boolean
424
     */
425
    final public function valid()
0 ignored issues
show
Coding Style introduced by
Unnecessary FINAL modifier in FINAL class
Loading history...
426
    {
427
        $key = key($this->childNodes);
428
429
        return ($key !== null && $key !== false);
430
    }
431
432
    /**
433
     * Rewinds the Iterator to the first element.
434
     *
435
     * @return void
436
     */
437
    final public function rewind()
0 ignored issues
show
Coding Style introduced by
Unnecessary FINAL modifier in FINAL class
Loading history...
438
    {
439
        reset($this->childNodes);
440
    }
441
}
442