Completed
Push — master ( c7f806...9f6188 )
by Gabor
04:00
created

AbstractElement::camelCaseToUnderscore()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
crap 3
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\Element\Traits\IteratorTrait;
21
use WebHemi\Validator\ValidatorInterface;
22
23
/**
24
 * Class AbstractElement
25
 */
26
abstract class AbstractElement implements FormElementInterface, Iterator
27
{
28
    /** @var string */
29
    protected $type = '';
30
    /** @var int */
31
    protected static $tabIndex = 1;
32
33
    /** @var string */
34
    protected $name;
35
    /** @var string */
36
    protected $label;
37
    /** @var mixed */
38
    protected $value;
39
    /** @var array */
40
    protected $attributes = [];
41
    /** @var array<ValidatorInterface> */
42
    protected $validators = [];
43
    /** @var array */
44
    protected $errors = [];
45
    /** @var FormElementInterface */
46
    protected $parentNode;
47
    /** @var array */
48
    protected $mandatoryParentTypes = [];
49
50
    // The implementation of the Iterator interface.
51
    use IteratorTrait;
52
53
    /**
54
     * AbstractFormElement constructor.
55
     *
56
     * @param string $name
57
     * @param string $label
58
     * @param mixed  $value
59
     */
60 16
    public function __construct($name = '', $label = '', $value = null)
61
    {
62 16
        $this->name = preg_replace('/[^a-z0-9]/', '_', strtolower($name));
63 16
        $this->label = $label;
64 16
        $this->value = $value;
65 16
    }
66
67
    /**
68
     * Returns the element type.
69
     *
70
     * @throws Exception
71
     * @return string
72
     */
73 2
    final public function getType()
74
    {
75 2
        if (empty($this->type)) {
76 1
            throw new Exception('You must specify the element type in the $type class property.');
77
        }
78
79 2
        return $this->type;
80
    }
81
82
    /**
83
     * Sets element name. The implementation should decide if it is allowed after init.
84
     *
85
     * @param string $name
86
     * @return AbstractElement
87
     */
88 9
    public function setName($name)
89
    {
90 9
        $this->name = $name;
91
92 9
        return $this;
93
    }
94
95
    /**
96
     * Returns the element name. If parameter is TRUE, then the method should include all the parents' names as well.
97
     *
98
     * @param boolean $getFulNodeName
99
     * @return string
100
     */
101 11
    public function getName($getFulNodeName = true)
102
    {
103 11
        $name = $this->name;
104
105 11
        if ($getFulNodeName) {
106 5
            if ($this->parentNode instanceof FormElementInterface) {
107 1
                $name = $this->parentNode->getName().'['.$this->name.']';
108 1
            }
109 5
        }
110
111 11
        return $name;
112
    }
113
114
    /**
115
     * Sets element label.
116
     *
117
     * @param string $label
118
     * @return AbstractElement
119
     */
120 1
    public function setLabel($label)
121
    {
122 1
        $this->label = $label;
123
124 1
        return $this;
125
    }
126
127
    /**
128
     * Returns the element label.
129
     *
130
     * @return string
131
     */
132 1
    public function getLabel()
133
    {
134 1
        return $this->label;
135
    }
136
137
    /**
138
     * Sets element value.
139
     *
140
     * @param mixed $value
141
     * @return AbstractElement
142
     */
143 8
    public function setValue($value)
144
    {
145 8
        $this->value = $value;
146
147 8
        return $this;
148
    }
149
150
    /**
151
     * Returns element value.
152
     *
153
     * @return mixed
154
     */
155 1
    public function getValue()
156
    {
157 1
        return $this->value;
158
    }
159
160
    /**
161
     * Gets element Id.
162
     *
163
     * @return string
164
     */
165 1
    public function getId()
166
    {
167 1
        $name = $this->getName();
168 1
        $md5Match = [];
169
170
        // Rip off the unique form prefix to make possible to work with fixed CSS id selectors.
171 1
        if (preg_match('/^.+(?P<md5>\_[a-f0-9]{32})($|\_.*$)/', $name, $md5Match)) {
172 1
            $name = str_replace($md5Match['md5'], '', $name);
173 1
        }
174
175 1
        $elementId = 'id_'.trim(preg_replace('/[^a-zA-Z0-9]/', '_', $name), '_');
176 1
        $elementId = $this->camelCaseToUnderscore($elementId);
177
178 1
        return str_replace('__', '_', $elementId);
179
    }
180
181
    /**
182
     * Converts CamelCase text to under_score equivalent.
183
     *
184
     * @param $input
185
     * @return string
186
     */
187 1
    private function camelCaseToUnderscore($input)
188
    {
189 1
        preg_match_all('/([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)/', $input, $matches);
190 1
        $return = $matches[0];
191
192 1
        foreach ($return as &$match) {
193 1
            $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
194 1
        }
195
196 1
        return implode('_', $return);
197
    }
198
199
    /**
200
     * Sets multiple attributes.
201
     *
202
     * @param array $attributes
203
     * @return AbstractElement
204
     */
205 10
    public function setAttributes(array $attributes)
206
    {
207 10
        $this->attributes = [];
208
209 10
        foreach ($attributes as $key => $value) {
210 10
            $this->setAttribute($key, $value);
211 9
        }
212
213 9
        return $this;
214
    }
215
216
    /**
217
     * Sets element attribute.
218
     *
219
     * @param string $key
220
     * @param mixed $value
221
     * @throws InvalidArgumentException
222
     * @return AbstractElement
223
     */
224 10
    protected function setAttribute($key, $value)
225
    {
226 10
        if (!is_scalar($value)) {
227 1
            throw new InvalidArgumentException('Element attribute can hold scalar data only.');
228
        }
229
230 9
        $this->attributes[$key] = $value;
231
232 9
        return $this;
233
    }
234
235
    /**
236
     * Gets all the attributes.
237
     *
238
     * @return array
239
     */
240 5
    public function getAttributes()
241
    {
242 5
        return $this->attributes;
243
    }
244
245
    /**
246
     * Gets element attribute.
247
     *
248
     * @param string $name
249
     * @return mixed
250
     */
251 1
    public function getAttribute($name)
252
    {
253 1
        if (!isset($this->attributes[$name])) {
254 1
            throw new InvalidArgumentException(sprintf('Invalid attribute: `%s`', $name));
255
        }
256
257 1
        return $this->attributes[$name];
258
    }
259
260
    /**
261
     * Sets and increments the tabulator index globally. This method should be used only on visible elements.
262
     *
263
     * @return AbstractElement
264
     */
265 15
    public function setTabIndex()
266
    {
267 15
        $this->attributes['tabindex'] = self::$tabIndex++;
268
269 15
        return $this;
270
    }
271
272
    /**
273
     * Sets the element errors. Usually the validator should set it, but it is allowed to set from outside too.
274
     *
275
     * @param array $errors
276
     * @return AbstractElement
277
     */
278 1
    public function setErrors(array $errors)
279
    {
280 1
        $this->errors = $errors;
281
282 1
        return $this;
283
    }
284
285
    /**
286
     * Checks if there are error messages set.
287
     *
288
     * @return boolean
289
     */
290 1
    public function hasErrors()
291
    {
292 1
        return !empty($this->errors);
293
    }
294
295
    /**
296
     * Gets validation errors.
297
     *
298
     * @return array
299
     */
300 2
    public function getErrors()
301
    {
302 2
        return $this->errors;
303
    }
304
305
    /**
306
     * Sets the element validators.
307
     *
308
     * @param array<ValidatorInterface> $validators
309
     * @return FormElementInterface
310
     */
311 1
    public function setValidators(array $validators)
312
    {
313 1
        $this->validators = [];
314
315 1
        foreach ($validators as $validator) {
316 1
            $this->addValidator($validator);
317 1
        }
318
319 1
        return $this;
320
    }
321
322
    /**
323
     * Adds validator to the form.
324
     *
325
     * @param ValidatorInterface $validator
326
     * @return AbstractElement
327
     */
328 1
    protected function addValidator(ValidatorInterface $validator)
329
    {
330 1
        $this->validators[] = $validator;
331
332 1
        return $this;
333
    }
334
335
    /**
336
     * Gets the element validators.
337
     *
338
     * @return array<ValidatorInterface>
339
     */
340 1
    public function getValidators()
341
    {
342 1
        return $this->validators;
343
    }
344
345
    /**
346
     * Validates element value.
347
     *
348
     * @param bool $reValidate
349
     * @return bool
350
     */
351 3
    public function isValid($reValidate = false)
352
    {
353 3
        if ($reValidate) {
354 2
            $this->errors = [];
355
356
            /** @var ValidatorInterface $validator */
357 2
            foreach ($this->validators as $validator) {
358 1
                if (!$validator->validate($this->value)) {
359 1
                    $this->errors[] = $validator->getError();
360 1
                }
361 2
            }
362 2
        }
363
364 3
        return empty($this->errors);
365
    }
366
367
    /**
368
     * Sets the parent element.
369
     *
370
     * @param FormElementInterface $formElement
371
     * @throws RuntimeException
372
     * @return AbstractElement
373
     */
374 10
    public function setParentNode(FormElementInterface $formElement)
375
    {
376 10
        if (!$formElement instanceof NestedElementInterface) {
377 1
            throw new RuntimeException(
378 1
                sprintf(
379 1
                    'Cannot set `%s` as child element of `%s`.',
380 1
                    $this->getType(),
381 1
                    $formElement->getType()
382 1
                )
383 1
            );
384
        }
385
386 10
        $this->parentNode = $formElement;
387
388 10
        return $this;
389
    }
390
391
    /**
392
     * Gets the parent element.
393
     *
394
     * @return FormElementInterface
395
     */
396 1
    public function getParentNode()
397
    {
398 1
        return $this->parentNode;
399
    }
400
}
401