Passed
Branch master (30ae21)
by Gabor
03:15
created

AbstractElement::setErrors()   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
c 0
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
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\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;
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...
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