Passed
Push — master ( 3c96ed...518a68 )
by Mikael
02:02
created

FormElement   C

Complexity

Total Complexity 78

Size/Duplication

Total Lines 551
Duplicated Lines 2 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 78.14%

Importance

Changes 0
Metric Value
dl 11
loc 551
ccs 168
cts 215
cp 0.7814
rs 5.4563
c 0
b 0
f 0
wmc 78
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
getHTML() 0 1 ?
A getValue() 0 4 1
A getEscapedValue() 0 4 1
A getRawValue() 0 4 1
A escValue() 0 4 1
A rawValue() 0 4 1
A setValue() 0 4 1
A checked() 0 4 1
A getValidationMessages() 0 12 3
F getHTMLDetails() 0 165 41
C validate() 0 82 9
A useNameAsDefaultLabel() 3 6 2
A useNameAsDefaultValue() 3 6 2
A value() 0 10 3
A offsetSet() 5 8 2
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
        $messages = $this->getValidationMessages();
272
273
        return [
274 9
            'id'             => $id,
275 9
            'class'          => $class,
276 9
            'wrapperElement' => $wrapperElement,
277 9
            'wrapperClass'   => $wrapperClass,
278 9
            'brAfterLabel'   => $brAfterLabel,
279 9
            'name'           => $name,
280 9
            'label'          => $label,
281 9
            'autofocus'      => $autofocus,
282 9
            'required'       => $required,
283 9
            'readonly'       => $readonly,
284 9
            'placeholder'    => $placeholder,
285 9
            'multiple'       => $multiple,
286 9
            'min'            => $min,
287 9
            'max'            => $max,
288 9
            'low'            => $low,
289 9
            'high'           => $high,
290 9
            'step'           => $step,
291 9
            'optimum'        => $optimum,
292 9
            'size'           => $size,
293 9
            'text'           => $text,
294 9
            'checked'        => $checked,
295 9
            'type'           => $type,
296 9
            'title'          => $title,
297 9
            'pattern'        => $pattern,
298 9
            'description'    => $description,
299 9
            'novalidate'     => $novalidate,
300 9
            'onlyValue'      => $onlyValue,
301 9
            'value'          => $value,
302 9
            'onclick'        => $onclick,
303 9
            'messages'       => $messages,
304
        ];
305
    }
306
307
308
309
    /**
310
     * Get HTML code for a element, must be implemented by each subclass.
311
     *
312
     * @return HTML code for the element.
313
     */
314
    abstract public function getHTML();
315
316
317
318
    /**
319
     * Validate the form element value according a ruleset.
320
     *
321
     * @param array     $rules validation rules.
322
     * @param Form|null $form  the parent form.
323
     *
324
     * @return boolean true if all rules pass, else false.
325
     */
326 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...
327
    {
328 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...
329
        $tests = [
330 2
            'fail' => [
331
                'message' => 'Will always fail.',
332
                'test' => 'return false;'
333
            ],
334
335
            'pass' => [
336
                'message' => 'Will always pass.',
337
                'test' => 'return true;'
338
            ],
339
340
            'not_empty' => [
341
                'message' => 'Can not be empty.',
342
                'test' => 'return $value != "";'
343
            ],
344
345
            'not_equal' => [
346
                'message' => 'Value not valid.',
347
                'test' => 'return $value != $arg;'
348
            ],
349
350
            'number' => [
351
                'message' => 'Must be a number.',
352
                'test' => 'return is_numeric($value);'
353
            ],
354
355
            'email' => [
356 2
                'message' => 'Must be an email adress.',
357 2
                'test' => function ($value) {
358 1
                    return preg_match('/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i', $value) === 1;
359 2
                }
360
            ],
361
362
            'match' => [
363
                'message' => 'The field does not match.',
364
                'test' => 'return $value == $form[$arg]["value"] ;'
365
            ],
366
367
            'must_accept' => [
368
                'message' => 'You must accept this.',
369
                'test' => 'return $checked;'
370
            ],
371
372
            'custom_test' => true,
373
        ];
374
375
        // max tecken, min tecken, datum, tid, datetime, mysql datetime
376
377 2
        $pass = true;
378 2
        $messages = array();
379 2
        $value = $this['value'];
380 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...
381
382 2
        foreach ($rules as $key => $val) {
383 2
            $rule = is_numeric($key) ? $val : $key;
384 2
            if (!isset($tests[$rule])) {
385 1
                throw new Exception("Validation of form element failed, no such validation rule exists: $rule");
386
            }
387 1
            $arg = is_numeric($key) ? null : $val;
388
389 1
            $test = ($rule == 'custom_test') ? $arg : $tests[$rule];
390 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...
391 1
            if (is_callable($test['test'])) {
392 1
                $status = $test['test']($value);
393
            } else {
394
                $status = eval($test['test']);
395
            }
396
397 1
            if ($status === false) {
398
                $messages[] = $test['message'];
399 1
                $pass = false;
400
            }
401
        }
402
403 1
        if (!empty($messages)) {
404
            $this['validation-messages'] = $messages;
405
        }
406 1
        return $pass;
407
    }
408
409
410
411
    /**
412
     * Use the element name as label if label is not set.
413
     *
414
     * @param string $append a colon as default to the end of the label.
415
     *
416
     * @return void
417
     */
418 19
    public function useNameAsDefaultLabel($append = ':')
419
    {
420 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...
421 19
            $this['label'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name']))).$append;
422
        }
423 19
    }
424
425
426
427
    /**
428
     * Use the element name as value if value is not set.
429
     *
430
     * @return void
431
     */
432 3
    public function useNameAsDefaultValue()
433
    {
434 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...
435 3
            $this['value'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name'])));
436
        }
437 3
    }
438
439
440
441
    /**
442
     * Get the value of the form element.
443
     *
444
     * @deprecated
445
     *
446
     * @return mixed the value of the form element.
447
     */
448
    public function getValue()
449
    {
450
        return $this['value'];
451
    }
452
453
454
455
    /**
456
     * Get the escaped value of the form element.
457
     *
458
     * @return mixed the value of the form element.
459
     */
460 5
    public function getEscapedValue()
461
    {
462 5
        return htmlentities($this['value']);
463
    }
464
465
466
467
    /**
468
     * Get the unescaped value of the form element.
469
     *
470
     * @return mixed the value of the form element.
471
     */
472 5
    public function getRawValue()
473
    {
474 5
        return $this['value'];
475
    }
476
477
478
479
    /**
480
     * Get the value of the form element and respect configuration
481
     * details whether it should be raw or escaped.
482
     *
483
     * @return mixed the value of the form element.
484
     */
485
    public function value()
486
    {
487
        $escape = isset($this->default["escape-values"])
488
            ? $this->default["escape-values"]
489
            : true;
490
491
        return $escape
492
            ? $this->getEscapedValue()
493
            : $this->getRawValue();
494
    }
495
496
497
498
    /**
499
     * Get the escaped value of the form element and respect configuration
500
     * details whether it should be raw or escaped.
501
     *
502
     * @return mixed the value of the form element.
503
     */
504 1
    public function escValue()
505
    {
506 1
        return $this->getEscapedValue();
507
    }
508
509
510
511
    /**
512
     * Get the raw value of the form element.
513
     *
514
     * @return mixed the value of the form element.
515
     */
516 1
    public function rawValue()
517
    {
518 1
        return $this->getRawValue();
519
    }
520
521
522
523
    /**
524
     * Set the value for the element.
525
     *
526
     * @param mixed $value set this to be the value of the formelement.
527
     *
528
     * @return mixed the value of the form element.
529
     */
530 1
    public function setValue($value)
531
    {
532 1
        return $this['value'] = $value;
533
    }
534
535
536
537
    /**
538
     * Get the status of the form element if it is checked or not.
539
     *
540
     * @return mixed the value of the form element.
541
     */
542
    public function checked()
543
    {
544
        return $this['checked'];
545
    }
546
547
548
549
    /**
550
     * Check if the element is a button.
551
     *
552
     * @return boolean true or false.
553
     */
554
    public function isButton()
555
    {
556
        return in_array($this["type"], ["submit", "reset", "button"]);
557
    }
558
}
559