Passed
Push — master ( 41990d...862f39 )
by Gabor
09:16 queued 04:24
created

AbstractElement::resetTabIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 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\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 26
    public function __construct($name = '', $label = '', $value = null)
64
    {
65 26
        $this->name = preg_replace('/[^a-z0-9]/', '_', strtolower($name));
66 26
        $this->label = $label;
67 26
        $this->value = $value;
68 26
    }
69
70
    /**
71
     * Resets the object when cloning.
72
     */
73 4
    public function __clone()
74
    {
75 4
        unset($this->parentNode);
76 4
        $this->name = '';
77 4
        $this->label = '';
78 4
        $this->value = '';
79 4
        $this->attributes = [];
80 4
        $this->validators = [];
81 4
        $this->errors = [];
82 4
        $this->mandatoryParentTypes = [];
83 4
    }
84
85
    /**
86
     * Returns the element type.
87
     *
88
     * @throws Exception
89
     * @return string
90
     */
91 2
    final public function getType()
92
    {
93 2
        if (empty($this->type)) {
94 1
            throw new Exception(
95 1
                sprintf(
96 1
                    'You must specify the element type in the $type class property. In %s',
97 1
                    get_called_class()
98 1
                )
99 1
            );
100
        }
101
102 2
        return $this->type;
103
    }
104
105
    /**
106
     * Sets element name. The implementation should decide if it is allowed after init.
107
     *
108
     * @param string $name
109
     * @return AbstractElement
110
     */
111 13
    public function setName($name)
112
    {
113 13
        $this->name = $name;
114
115 13
        return $this;
116
    }
117
118
    /**
119
     * Returns the element name. If parameter is TRUE, then the method should include all the parents' names as well.
120
     *
121
     * @param boolean $getFulNodeName
122
     * @return string
123
     */
124 14
    public function getName($getFulNodeName = true)
125
    {
126 14
        $name = $this->name;
127
128 14
        if ($getFulNodeName && isset($this->parentNode)) {
129 1
            if ($this->parentNode instanceof FormElementInterface) {
130 1
                $name = $this->parentNode->getName().'['.$this->name.']';
131 1
            }
132 1
        }
133
134 14
        return $name;
135
    }
136
137
    /**
138
     * Sets element label.
139
     *
140
     * @param string $label
141
     * @return AbstractElement
142
     */
143 2
    public function setLabel($label)
144
    {
145 2
        $this->label = $label;
146
147 2
        return $this;
148
    }
149
150
    /**
151
     * Returns the element label.
152
     *
153
     * @return string
154
     */
155 2
    public function getLabel()
156
    {
157 2
        return $this->label;
158
    }
159
160
    /**
161
     * Sets element value.
162
     *
163
     * @param mixed $value
164
     * @return AbstractElement
165
     */
166 9
    public function setValue($value)
167
    {
168 9
        $this->value = $value;
169
170 9
        return $this;
171
    }
172
173
    /**
174
     * Returns element value.
175
     *
176
     * @return mixed
177
     */
178 2
    public function getValue()
179
    {
180 2
        return $this->value;
181
    }
182
183
    /**
184
     * Gets element Id.
185
     *
186
     * @return string
187
     */
188 1
    public function getId()
189
    {
190 1
        $name = $this->getName();
191 1
        $md5Match = [];
192
193
        // Rip off the unique form prefix to make possible to work with fixed CSS id selectors.
194 1
        if (preg_match('/^.+(?P<md5>\_[a-f0-9]{32})($|\_.*$)/', $name, $md5Match)) {
195 1
            $name = str_replace($md5Match['md5'], '', $name);
196 1
        }
197
198 1
        $elementId = 'id_'.trim(preg_replace('/[^a-zA-Z0-9]/', '_', $name), '_');
199 1
        $elementId = $this->camelCaseToUnderscore($elementId);
200
201 1
        return str_replace('__', '_', $elementId);
202
    }
203
204
    /**
205
     * Sets multiple attributes.
206
     *
207
     * @param array $attributes
208
     * @return AbstractElement
209
     */
210 13
    public function setAttributes(array $attributes)
211
    {
212 13
        $this->attributes = [];
213
214 13
        return $this->addAttributes($attributes);
215
    }
216
217
    /**
218
     * Adds multiple attributes.
219
     *
220
     * @param array $attributes
221
     * @return AbstractElement
222
     */
223 13
    public function addAttributes(array $attributes)
224
    {
225 13
        foreach ($attributes as $key => $value) {
226 13
            $this->setAttribute($key, $value);
227 12
        }
228
229 12
        return $this;
230
    }
231
232
    /**
233
     * Sets element attribute.
234
     *
235
     * @param string $key
236
     * @param mixed $value
237
     * @throws InvalidArgumentException
238
     * @return AbstractElement
239
     */
240 13
    protected function setAttribute($key, $value)
241
    {
242 13
        $forbiddenAttributes = ['name', 'id'];
243
244
        // Skip forbidden attributes.
245 13
        if (in_array($key, $forbiddenAttributes)) {
246 1
            return $this;
247
        }
248
249 12
        if (!is_scalar($value)) {
250 1
            throw new InvalidArgumentException('Element attribute can hold scalar data only.');
251
        }
252
253 11
        $this->attributes[$key] = $value;
254
255 11
        return $this;
256
    }
257
258
    /**
259
     * Gets all the attributes.
260
     *
261
     * @return array
262
     */
263 6
    public function getAttributes()
264
    {
265 6
        return $this->attributes;
266
    }
267
268
    /**
269
     * Gets element attribute.
270
     *
271
     * @param string $name
272
     * @return mixed
273
     */
274 3
    public function getAttribute($name)
275
    {
276 3
        if (!isset($this->attributes[$name])) {
277 3
            throw new InvalidArgumentException(sprintf('Invalid attribute: `%s`', $name));
278
        }
279
280 3
        return $this->attributes[$name];
281
    }
282
283
    /**
284
     * Resets the tabulator index internal counter.
285
     *
286
     * @return AbstractElement
287
     */
288 5
    public function resetTabIndex()
289
    {
290 5
        self::$tabIndex = 1;
291
292 5
        return $this;
293
    }
294
295
    /**
296
     * Sets and increments the tabulator index globally. This method should be used only on visible elements.
297
     *
298
     * @return AbstractElement
299
     */
300 25
    public function setTabIndex()
301
    {
302 25
        $this->attributes['tabindex'] = self::$tabIndex++;
303
304 25
        return $this;
305
    }
306
307
    /**
308
     * Sets the element errors. Usually the validator should set it, but it is allowed to set from outside too.
309
     *
310
     * @param array $errors
311
     * @return AbstractElement
312
     */
313 1
    public function setErrors(array $errors)
314
    {
315 1
        $this->errors = $errors;
316
317 1
        return $this;
318
    }
319
320
    /**
321
     * Checks if there are error messages set.
322
     *
323
     * @return boolean
324
     */
325 1
    public function hasErrors()
326
    {
327 1
        return !empty($this->errors);
328
    }
329
330
    /**
331
     * Gets validation errors.
332
     *
333
     * @return array
334
     */
335 2
    public function getErrors()
336
    {
337 2
        return $this->errors;
338
    }
339
340
    /**
341
     * Sets the element validators.
342
     *
343
     * @param array<ValidatorInterface> $validators
344
     * @return FormElementInterface
345
     */
346 1
    public function setValidators(array $validators)
347
    {
348 1
        $this->validators = [];
349
350 1
        foreach ($validators as $validator) {
351 1
            $this->addValidator($validator);
352 1
        }
353
354 1
        return $this;
355
    }
356
357
    /**
358
     * Adds validator to the form.
359
     *
360
     * @param ValidatorInterface $validator
361
     * @return AbstractElement
362
     */
363 1
    protected function addValidator(ValidatorInterface $validator)
364
    {
365 1
        $this->validators[] = $validator;
366
367 1
        return $this;
368
    }
369
370
    /**
371
     * Gets the element validators.
372
     *
373
     * @return array<ValidatorInterface>
374
     */
375 1
    public function getValidators()
376
    {
377 1
        return $this->validators;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->validators; (WebHemi\Validator\ValidatorInterface[]) is incompatible with the return type declared by the interface WebHemi\Form\Element\For...nterface::getValidators of type WebHemi\Form\Element\ValidatorInterface[].

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
378
    }
379
380
    /**
381
     * Validates element value.
382
     *
383
     * @param bool $reValidate
384
     * @return bool
385
     */
386 3
    public function isValid($reValidate = false)
387
    {
388 3
        if ($reValidate) {
389 2
            $this->errors = [];
390
391
            /** @var ValidatorInterface $validator */
392 2
            foreach ($this->validators as $validator) {
393 1
                if (!$validator->validate($this->value)) {
394 1
                    $this->errors[] = $validator->getError();
395 1
                }
396 2
            }
397 2
        }
398
399 3
        return empty($this->errors);
400
    }
401
402
    /**
403
     * Sets the parent element.
404
     *
405
     * @param FormElementInterface $formElement
406
     * @throws RuntimeException
407
     * @return AbstractElement
408
     */
409 10
    public function setParentNode(FormElementInterface $formElement)
410
    {
411 10
        if (!$formElement instanceof NestedElementInterface) {
412 1
            throw new RuntimeException(
413 1
                sprintf(
414 1
                    'Cannot set `%s` as child element of `%s`.',
415 1
                    $this->getType(),
416 1
                    $formElement->getType()
417 1
                )
418 1
            );
419
        }
420
421 10
        $this->parentNode = $formElement;
422
423 10
        return $this;
424
    }
425
426
    /**
427
     * Gets the parent element.
428
     *
429
     * @return FormElementInterface
430
     */
431 1
    public function getParentNode()
432
    {
433 1
        return $this->parentNode;
434
    }
435
}
436