Passed
Push — master ( 518a68...fbf0f1 )
by Mikael
42s queued 10s
created

FormElement   C

Complexity

Total Complexity 79

Size/Duplication

Total Lines 556
Duplicated Lines 1.98 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 78.08%

Importance

Changes 0
Metric Value
dl 11
loc 556
ccs 171
cts 219
cp 0.7808
rs 5.442
c 0
b 0
f 0
wmc 79
lcom 2
cbo 1

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A setDefault() 0 4 1
A offsetExists() 0 4 1
A offsetUnset() 0 4 1
A offsetGet() 0 4 2
A getElementId() 0 4 2
A getValidationMessages() 0 12 3
A offsetSet() 5 8 2
F getHTMLDetails() 0 170 42
getHTML() 0 1 ?
C validate() 0 82 9
A useNameAsDefaultLabel() 3 6 2
A useNameAsDefaultValue() 3 6 2
A getValue() 0 4 1
A getEscapedValue() 0 4 1
A getRawValue() 0 4 1
A value() 0 10 3
A escValue() 0 4 1
A rawValue() 0 4 1
A setValue() 0 4 1
A checked() 0 4 1
A isButton() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FormElement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FormElement, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Anax\HTMLForm;
4
5
/**
6
 * A utility class to easy creating and handling of forms
7
 */
8
abstract class FormElement implements \ArrayAccess
9
{
10
11
    /**
12
     * @var array $attributes        settings to use to create element
13
     * @var array $config            default settings to use to create element
14
     * @var array $characterEncoding setting for character encoding
15
     */
16
    public $attributes;
17
    public $config;
18
    public $characterEncoding;
19
20
21
22
    /**
23
     * Constructor creating a form element.
24
     *
25
     * @param string $name       of the element.
26
     * @param array  $attributes to set to the element. Default is an empty
27
     *                           array.
28
     */
29 26
    public function __construct($name, $attributes = [])
30
    {
31 26
        $this->attributes = $attributes;
32 26
        $this['name'] = $name;
33
        //$this['key'] = $name;
34
        //$this['name'] = isset($this['name']) ? $this['name'] : $name;
35
36 26
        $this->characterEncoding = 'UTF-8';
37 26
        $this->default["wrapper-element"] = "p";
0 ignored issues
show
Bug introduced by
The property default does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
38 26
        $this->default["br-after-label"] = true;
39 26
        $this->default["escape-values"] = true;
40 26
    }
41
42
43
44
    /**
45
     * Set default values to use, merge incoming with existing.
46
     *
47
     * @param array  $options key value array with settings to use.
48
     *
49
     * @return void
50
     */
51 4
    public function setDefault($options)
52
    {
53 4
        $this->default = array_merge($this->default, $options);
54 4
    }
55
56
57
58
    /**
59
     * Implementing ArrayAccess for this->attributes
60
     *
61
     * @return void
62
     */
63 26
    public function offsetSet($offset, $value)
64
    {
65 26 View Code Duplication
        if (is_null($offset)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
66
            $this->attributes[] = $value;
67
        } else {
68 26
            $this->attributes[$offset] = $value;
69
        }
70 26
    }
71
72
73
74
    /**
75
     * Implementing ArrayAccess for this->attributes
76
     */
77 26
    public function offsetExists($offset)
78
    {
79 26
        return isset($this->attributes[$offset]);
80
    }
81
82
83
84
    /**
85
     * Implementing ArrayAccess for this->attributes
86
     */
87
    public function offsetUnset($offset)
88
    {
89
        unset($this->attributes[$offset]);
90
    }
91
92
93
94
    /**
95
     * Implementing ArrayAccess for this->attributes
96
     */
97 25
    public function offsetGet($offset)
98
    {
99 25
        return isset($this->attributes[$offset]) ? $this->attributes[$offset] : null;
100
    }
101
102
103
104
    /**
105
     * Get id of an element.
106
     *
107
     * @return HTML code for the element.
108
     */
109 9
    public function getElementId()
110
    {
111 9
        return ($this['id'] = isset($this['id']) ? $this['id'] : 'form-element-' . $this['name']);
112
    }
113
114
115
116
    /**
117
     * Get alll validation messages.
118
     *
119
     * @return HTML code for the element.
120
     */
121 9
    public function getValidationMessages()
122
    {
123 9
        $messages = null;
124 9
        if (isset($this['validation-messages'])) {
125
            $message = null;
126
            foreach ($this['validation-messages'] as $val) {
127
                $message .= "<li>{$val}</li>\n";
128
            }
129
            $messages = "<ul class='validation-message'>\n{$message}</ul>\n";
130
        }
131 9
        return $messages;
132
    }
133
134
135
136
    /**
137
     * Get details for a HTML element, prepare for creating HTML code for it.
138
     *
139
     * @return HTML code for the element.
140
     */
141 9
    public function getHTMLDetails()
142
    {
143
        // Add disabled to be able to disable a form element
144
        // Add maxlength
145 9
        $id =  $this->getElementId();
146
147 9
        $class = isset($this['class'])
148
            ? "{$this['class']}"
149 9
            : null;
150
151 9
        $validates = (isset($this['validation-pass']) && $this['validation-pass'] === false)
152
            ? ' validation-failed'
153 9
            : null;
154
155 9
        $class = (isset($class) || isset($validates))
156
            ? " class='{$class}{$validates}'"
157 9
            : null;
158
159 9
        $wrapperElement = isset($this['wrapper-element'])
160
            ? $this['wrapper-element']
161 9
            : $this->default["wrapper-element"];
162
163 9
        $wrapperClass = isset($this['wrapper-class'])
164
            ? " class=\"{$this['wrapper-class']}\""
165 9
            : null;
166
167 9
        $brAfterLabel = isset($this['br-after-label'])
168
            ? $this['br-after-label']
169 9
            : $this->default["br-after-label"];
170
171 9
        $brAfterLabel = $brAfterLabel
172 9
            ? "<br>"
173 9
            : null;
174
175 9
        $name = " name='{$this['name']}'";
176
177 9
        $label = isset($this['label'])
178 5
            ? ($this['label'] . (isset($this['required']) && $this['required']
179
                ? "<span class='form-element-required'>*</span>"
180 5
                : null))
181 9
            : null;
182
183 9
        $autofocus = isset($this['autofocus']) && $this['autofocus']
184
            ? " autofocus='autofocus'"
185 9
            : null;
186
187 9
        $required = isset($this['required']) && $this['required']
188
            ? " required='required'"
189 9
            : null;
190
191 9
        $readonly = isset($this['readonly']) && $this['readonly']
192
            ? " readonly='readonly'"
193 9
            : null;
194
195 9
        $placeholder = isset($this['placeholder']) && $this['placeholder']
196
            ? " placeholder='{$this['placeholder']}'"
197 9
            : null;
198
199 9
        $multiple = isset($this['multiple']) && $this['multiple']
200
            ? " multiple"
201 9
            : null;
202
203 9
        $max = isset($this['max'])
204
            ? " max='{$this['max']}'"
205 9
            : null;
206
207 9
        $min = isset($this['min'])
208
            ? " min='{$this['min']}'"
209 9
            : null;
210
211 9
        $low = isset($this['low'])
212
            ? " low='{$this['low']}'"
213 9
            : null;
214
215 9
        $high = isset($this['high'])
216
            ? " high='{$this['high']}'"
217 9
            : null;
218
219 9
        $optimum = isset($this['optimum'])
220
            ? " optimum='{$this['optimum']}'"
221 9
            : null;
222
223 9
        $step = isset($this['step'])
224
            ? " step='{$this['step']}'"
225 9
            : null;
226
227 9
        $size = isset($this['size'])
228
            ? " size='{$this['size']}'"
229 9
            : null;
230
231 9
        $text = isset($this['text'])
232
            ? htmlentities($this['text'], ENT_QUOTES, $this->characterEncoding)
233 9
            : null;
234
235 9
        $checked = isset($this['checked']) && $this['checked']
236
            ? " checked='checked'"
237 9
            : null;
238
239 9
        $type = isset($this['type'])
240 9
            ? " type='{$this['type']}'"
241 9
            : null;
242
243 9
        $title = isset($this['title'])
244
            ? " title='{$this['title']}'"
245 9
            : null;
246
247 9
        $pattern = isset($this['pattern'])
248
            ? " pattern='{$this['pattern']}'"
249 9
            : null;
250
251 9
        $description = isset($this['description'])
252
            ? $this['description']
253 9
            : null;
254
255 9
        $novalidate = isset($this['formnovalidate'])
256 1
            ? " formnovalidate='formnovalidate'"
257 9
            : null;
258
259 9
        $onlyValue = isset($this['value'])
260 2
            ? htmlentities($this['value'], ENT_QUOTES, $this->characterEncoding)
261 9
            : null;
262
263 9
        $value = isset($this['value'])
264 2
            ? " value='{$onlyValue}'"
265 9
            : null;
266
267 9
        $onclick = isset($this['onclick'])
268
            ? " onclick=\"{$this['onclick']}\""
269 9
            : null;
270
271 9
        $maxlength = isset($this['maxlength'])
272
            ? " maxlength='{$this['maxlength']}'"
273 9
            : null;
274
275 9
        $messages = $this->getValidationMessages();
276
277
        return [
278 9
            'id'             => $id,
279 9
            'class'          => $class,
280 9
            'wrapperElement' => $wrapperElement,
281 9
            'wrapperClass'   => $wrapperClass,
282 9
            'brAfterLabel'   => $brAfterLabel,
283 9
            'name'           => $name,
284 9
            'label'          => $label,
285 9
            'autofocus'      => $autofocus,
286 9
            'required'       => $required,
287 9
            'readonly'       => $readonly,
288 9
            'placeholder'    => $placeholder,
289 9
            'multiple'       => $multiple,
290 9
            'min'            => $min,
291 9
            'max'            => $max,
292 9
            'maxlength'      => $maxlength,
293 9
            'low'            => $low,
294 9
            'high'           => $high,
295 9
            'step'           => $step,
296 9
            'optimum'        => $optimum,
297 9
            'size'           => $size,
298 9
            'text'           => $text,
299 9
            'checked'        => $checked,
300 9
            'type'           => $type,
301 9
            'title'          => $title,
302 9
            'pattern'        => $pattern,
303 9
            'description'    => $description,
304 9
            'novalidate'     => $novalidate,
305 9
            'onlyValue'      => $onlyValue,
306 9
            'value'          => $value,
307 9
            'onclick'        => $onclick,
308 9
            'messages'       => $messages,
309
        ];
310
    }
311
312
313
314
    /**
315
     * Get HTML code for a element, must be implemented by each subclass.
316
     *
317
     * @return HTML code for the element.
318
     */
319
    abstract public function getHTML();
320
321
322
323
    /**
324
     * Validate the form element value according a ruleset.
325
     *
326
     * @param array     $rules validation rules.
327
     * @param Form|null $form  the parent form.
328
     *
329
     * @return boolean true if all rules pass, else false.
330
     */
331 2
    public function validate($rules, $form = null)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
332
    {
333 2
        $regExpEmailAddress = '/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i';
0 ignored issues
show
Unused Code introduced by
$regExpEmailAddress is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
334
        $tests = [
335 2
            'fail' => [
336
                'message' => 'Will always fail.',
337
                'test' => 'return false;'
338
            ],
339
340
            'pass' => [
341
                'message' => 'Will always pass.',
342
                'test' => 'return true;'
343
            ],
344
345
            'not_empty' => [
346
                'message' => 'Can not be empty.',
347
                'test' => 'return $value != "";'
348
            ],
349
350
            'not_equal' => [
351
                'message' => 'Value not valid.',
352
                'test' => 'return $value != $arg;'
353
            ],
354
355
            'number' => [
356
                'message' => 'Must be a number.',
357
                'test' => 'return is_numeric($value);'
358
            ],
359
360
            'email' => [
361 2
                'message' => 'Must be an email adress.',
362 2
                'test' => function ($value) {
363 1
                    return preg_match('/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i', $value) === 1;
364 2
                }
365
            ],
366
367
            'match' => [
368
                'message' => 'The field does not match.',
369
                'test' => 'return $value == $form[$arg]["value"] ;'
370
            ],
371
372
            'must_accept' => [
373
                'message' => 'You must accept this.',
374
                'test' => 'return $checked;'
375
            ],
376
377
            'custom_test' => true,
378
        ];
379
380
        // max tecken, min tecken, datum, tid, datetime, mysql datetime
381
382 2
        $pass = true;
383 2
        $messages = array();
384 2
        $value = $this['value'];
385 2
        $checked = $this['checked'];
0 ignored issues
show
Unused Code introduced by
$checked is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
386
387 2
        foreach ($rules as $key => $val) {
388 2
            $rule = is_numeric($key) ? $val : $key;
389 2
            if (!isset($tests[$rule])) {
390 1
                throw new Exception("Validation of form element failed, no such validation rule exists: $rule");
391
            }
392 1
            $arg = is_numeric($key) ? null : $val;
393
394 1
            $test = ($rule == 'custom_test') ? $arg : $tests[$rule];
395 1
            $status = null;
0 ignored issues
show
Unused Code introduced by
$status is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
396 1
            if (is_callable($test['test'])) {
397 1
                $status = $test['test']($value);
398
            } else {
399
                $status = eval($test['test']);
400
            }
401
402 1
            if ($status === false) {
403
                $messages[] = $test['message'];
404 1
                $pass = false;
405
            }
406
        }
407
408 1
        if (!empty($messages)) {
409
            $this['validation-messages'] = $messages;
410
        }
411 1
        return $pass;
412
    }
413
414
415
416
    /**
417
     * Use the element name as label if label is not set.
418
     *
419
     * @param string $append a colon as default to the end of the label.
420
     *
421
     * @return void
422
     */
423 19
    public function useNameAsDefaultLabel($append = ':')
424
    {
425 19 View Code Duplication
        if (!isset($this['label'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
426 19
            $this['label'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name']))).$append;
427
        }
428 19
    }
429
430
431
432
    /**
433
     * Use the element name as value if value is not set.
434
     *
435
     * @return void
436
     */
437 3
    public function useNameAsDefaultValue()
438
    {
439 3 View Code Duplication
        if (!isset($this['value'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440 3
            $this['value'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name'])));
441
        }
442 3
    }
443
444
445
446
    /**
447
     * Get the value of the form element.
448
     *
449
     * @deprecated
450
     *
451
     * @return mixed the value of the form element.
452
     */
453
    public function getValue()
454
    {
455
        return $this['value'];
456
    }
457
458
459
460
    /**
461
     * Get the escaped value of the form element.
462
     *
463
     * @return mixed the value of the form element.
464
     */
465 5
    public function getEscapedValue()
466
    {
467 5
        return htmlentities($this['value']);
468
    }
469
470
471
472
    /**
473
     * Get the unescaped value of the form element.
474
     *
475
     * @return mixed the value of the form element.
476
     */
477 5
    public function getRawValue()
478
    {
479 5
        return $this['value'];
480
    }
481
482
483
484
    /**
485
     * Get the value of the form element and respect configuration
486
     * details whether it should be raw or escaped.
487
     *
488
     * @return mixed the value of the form element.
489
     */
490
    public function value()
491
    {
492
        $escape = isset($this->default["escape-values"])
493
            ? $this->default["escape-values"]
494
            : true;
495
496
        return $escape
497
            ? $this->getEscapedValue()
498
            : $this->getRawValue();
499
    }
500
501
502
503
    /**
504
     * Get the escaped value of the form element and respect configuration
505
     * details whether it should be raw or escaped.
506
     *
507
     * @return mixed the value of the form element.
508
     */
509 1
    public function escValue()
510
    {
511 1
        return $this->getEscapedValue();
512
    }
513
514
515
516
    /**
517
     * Get the raw value of the form element.
518
     *
519
     * @return mixed the value of the form element.
520
     */
521 1
    public function rawValue()
522
    {
523 1
        return $this->getRawValue();
524
    }
525
526
527
528
    /**
529
     * Set the value for the element.
530
     *
531
     * @param mixed $value set this to be the value of the formelement.
532
     *
533
     * @return mixed the value of the form element.
534
     */
535 1
    public function setValue($value)
536
    {
537 1
        return $this['value'] = $value;
538
    }
539
540
541
542
    /**
543
     * Get the status of the form element if it is checked or not.
544
     *
545
     * @return mixed the value of the form element.
546
     */
547
    public function checked()
548
    {
549
        return $this['checked'];
550
    }
551
552
553
554
    /**
555
     * Check if the element is a button.
556
     *
557
     * @return boolean true or false.
558
     */
559
    public function isButton()
560
    {
561
        return in_array($this["type"], ["submit", "reset", "button"]);
562
    }
563
}
564