Passed
Push — master ( d26101...1eb4af )
by Gabor
03:11
created

AbstractElement::setName()   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 1
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\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 21
    public function __construct($name = '', $label = '', $value = null)
61
    {
62 21
        $this->name = preg_replace('/[^a-z0-9]/', '_', strtolower($name));
63 21
        $this->label = $label;
64 21
        $this->value = $value;
65 21
    }
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 12
    public function getName($getFulNodeName = true)
102
    {
103 12
        $name = $this->name;
104
105 12
        if ($getFulNodeName) {
106 6
            if ($this->parentNode instanceof FormElementInterface) {
107 1
                $name = $this->parentNode->getName().'['.$this->name.']';
108 1
            }
109 6
        }
110
111 12
        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 11
    public function setAttributes(array $attributes)
206
    {
207 11
        $this->attributes = [];
208
209 11
        foreach ($attributes as $key => $value) {
210 11
            $this->setAttribute($key, $value);
211 10
        }
212
213 10
        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 11
    protected function setAttribute($key, $value)
225
    {
226 11
        if (!is_scalar($value)) {
227 1
            throw new InvalidArgumentException('Element attribute can hold scalar data only.');
228
        }
229
230 10
        $this->attributes[$key] = $value;
231
232 10
        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 2
    public function getAttribute($name)
252
    {
253 2
        if (!isset($this->attributes[$name])) {
254 2
            throw new InvalidArgumentException(sprintf('Invalid attribute: `%s`', $name));
255
        }
256
257 2
        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
     * @param bool $reset
264
     * @return AbstractElement
265
     */
266 20
    public function setTabIndex($reset = false)
267
    {
268 20
        if ($reset) {
269 1
            self::$tabIndex = 1;
270 1
        }
271
272 20
        $this->attributes['tabindex'] = self::$tabIndex++;
273
274 20
        return $this;
275
    }
276
277
    /**
278
     * Sets the element errors. Usually the validator should set it, but it is allowed to set from outside too.
279
     *
280
     * @param array $errors
281
     * @return AbstractElement
282
     */
283 1
    public function setErrors(array $errors)
284
    {
285 1
        $this->errors = $errors;
286
287 1
        return $this;
288
    }
289
290
    /**
291
     * Checks if there are error messages set.
292
     *
293
     * @return boolean
294
     */
295 1
    public function hasErrors()
296
    {
297 1
        return !empty($this->errors);
298
    }
299
300
    /**
301
     * Gets validation errors.
302
     *
303
     * @return array
304
     */
305 2
    public function getErrors()
306
    {
307 2
        return $this->errors;
308
    }
309
310
    /**
311
     * Sets the element validators.
312
     *
313
     * @param array<ValidatorInterface> $validators
314
     * @return FormElementInterface
315
     */
316 1
    public function setValidators(array $validators)
317
    {
318 1
        $this->validators = [];
319
320 1
        foreach ($validators as $validator) {
321 1
            $this->addValidator($validator);
322 1
        }
323
324 1
        return $this;
325
    }
326
327
    /**
328
     * Adds validator to the form.
329
     *
330
     * @param ValidatorInterface $validator
331
     * @return AbstractElement
332
     */
333 1
    protected function addValidator(ValidatorInterface $validator)
334
    {
335 1
        $this->validators[] = $validator;
336
337 1
        return $this;
338
    }
339
340
    /**
341
     * Gets the element validators.
342
     *
343
     * @return array<ValidatorInterface>
344
     */
345 1
    public function getValidators()
346
    {
347 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...
348
    }
349
350
    /**
351
     * Validates element value.
352
     *
353
     * @param bool $reValidate
354
     * @return bool
355
     */
356 3
    public function isValid($reValidate = false)
357
    {
358 3
        if ($reValidate) {
359 2
            $this->errors = [];
360
361
            /** @var ValidatorInterface $validator */
362 2
            foreach ($this->validators as $validator) {
363 1
                if (!$validator->validate($this->value)) {
364 1
                    $this->errors[] = $validator->getError();
365 1
                }
366 2
            }
367 2
        }
368
369 3
        return empty($this->errors);
370
    }
371
372
    /**
373
     * Sets the parent element.
374
     *
375
     * @param FormElementInterface $formElement
376
     * @throws RuntimeException
377
     * @return AbstractElement
378
     */
379 10
    public function setParentNode(FormElementInterface $formElement)
380
    {
381 10
        if (!$formElement instanceof NestedElementInterface) {
382 1
            throw new RuntimeException(
383 1
                sprintf(
384 1
                    'Cannot set `%s` as child element of `%s`.',
385 1
                    $this->getType(),
386 1
                    $formElement->getType()
387 1
                )
388 1
            );
389
        }
390
391 10
        $this->parentNode = $formElement;
392
393 10
        return $this;
394
    }
395
396
    /**
397
     * Gets the parent element.
398
     *
399
     * @return FormElementInterface
400
     */
401 1
    public function getParentNode()
402
    {
403 1
        return $this->parentNode;
404
    }
405
}
406