Completed
Push — master ( ed2b3e...68d467 )
by Mikael
02:34
created

FormElement   D

Complexity

Total Complexity 80

Size/Duplication

Total Lines 577
Duplicated Lines 1.04 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 68.53%

Importance

Changes 0
Metric Value
dl 6
loc 577
ccs 196
cts 286
cp 0.6853
rs 4.8717
c 0
b 0
f 0
wmc 80
lcom 2
cbo 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A offsetExists() 0 4 1
A offsetUnset() 0 4 1
A offsetGet() 0 4 2
B create() 0 50 4
A getElementId() 0 4 2
A getValidationMessages() 0 12 3
F getHTMLDetails() 0 141 36
A offsetSet() 0 8 2
C getHTML() 0 90 12
C validate() 0 82 9
A useNameAsDefaultLabel() 3 6 2
A useNameAsDefaultValue() 3 6 2
A getValue() 0 4 1
A value() 0 4 1
A checked() 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
class FormElement implements \ArrayAccess
9
{
10
11
    /**
12
     * Properties
13
     */
14
    public $attributes;
15
    public $characterEncoding;
16
17
18
19
    /**
20
     * Constructor creating a form element.
21
     *
22
     * @param string $name       of the element.
23
     * @param array  $attributes to set to the element. Default is an empty
24
     *                           array.
25
     */
26 21
    public function __construct($name, $attributes = [])
27
    {
28 21
        $this->attributes = $attributes;
29 21
        $this['name'] = $name;
30
        //$this['key'] = $name;
31
        //$this['name'] = isset($this['name']) ? $this['name'] : $name;
32
33 21
        $this->characterEncoding = 'UTF-8';
34 21
    }
35
  
36
  
37
38
    /**
39
     * Implementing ArrayAccess for this->attributes
40
     *
41
     * @return void
42
     */
43 21
    public function offsetSet($offset, $value)
44
    {
45 21
        if (is_null($offset)) {
46
            $this->attributes[] = $value;
47
        } else {
48 21
            $this->attributes[$offset] = $value;
49
        }
50 21
    }
51
    
52
53
54
    /**
55
     * Implementing ArrayAccess for this->attributes
56
     */
57 17
    public function offsetExists($offset)
58
    {
59 17
        return isset($this->attributes[$offset]);
60
    }
61
    
62
63
64
    /**
65
     * Implementing ArrayAccess for this->attributes
66
     */
67
    public function offsetUnset($offset)
68
    {
69
        unset($this->attributes[$offset]);
70
    }
71
    
72
73
74
    /**
75
     * Implementing ArrayAccess for this->attributes
76
     */
77 20
    public function offsetGet($offset)
78
    {
79 20
        return isset($this->attributes[$offset]) ? $this->attributes[$offset] : null;
80
    }
81
82
83
84
    /**
85
     * Create a formelement from an array, factory returns the correct
86
     * instance.
87
     *
88
     * @param string $name       name of the element.
89
     * @param array  $attributes to use when creating the element.
90
     *
91
     * @return the instance of the form element.
92
     */
93
    public static function create($name, $attributes)
94
    {
95
        // Not supported is type=image, <button>, list, output, select-optgroup
96
        $types = [
97
98
            // Standard HTML 4.01
99
            'text'              => '\Anax\HTMLForm\FormElementText',
100
            'file'              => '\Anax\HTMLForm\FormElementFile',
101
            'password'          => '\Anax\HTMLForm\FormElementPassword',
102
            'hidden'            => '\Anax\HTMLForm\FormElementHidden',
103
            'textarea'          => '\Anax\HTMLForm\FormElementTextarea',
104
            'radio'             => '\Anax\HTMLForm\FormElementRadio',
105
            'checkbox'          => '\Anax\HTMLForm\FormElementCheckbox',
106
            'select'            => '\Anax\HTMLForm\FormElementSelect',
107
            'select-multiple'   => '\Anax\HTMLForm\FormElementSelectMultiple',
108
            'submit'            => '\Anax\HTMLForm\FormElementSubmit',
109
            'reset'             => '\Anax\HTMLForm\FormElementReset',
110
            'button'            => '\Anax\HTMLForm\FormElementButton',
111
112
            // HTML5
113
            'color'             => '\Anax\HTMLForm\FormElementColor',
114
            'date'              => '\Anax\HTMLForm\FormElementDate',
115
            'number'            => '\Anax\HTMLForm\FormElementNumber',
116
            'range'             => '\Anax\HTMLForm\FormElementRange',
117
            'tel'               => '\Anax\HTMLForm\FormElementTel',
118
            'email'             => '\Anax\HTMLForm\FormElementEmail',
119
            'url'               => '\Anax\HTMLForm\FormElementUrl',
120
            'search'            => '\Anax\HTMLForm\FormElementSearch',
121
            'file-multiple'     => '\Anax\HTMLForm\FormElementFileMultiple',
122
            'datetime'          => '\Anax\HTMLForm\FormElementDatetime',
123
            'datetime-local'    => '\Anax\HTMLForm\FormElementDatetimeLocal',
124
            'month'             => '\Anax\HTMLForm\FormElementMonth',
125
            'time'              => '\Anax\HTMLForm\FormElementTime',
126
            'week'              => '\Anax\HTMLForm\FormElementWeek',
127
128
            // Custom
129
            'search-widget'     => '\Anax\HTMLForm\FormElementSearchWidget',
130
            'checkbox-multiple' => '\Anax\HTMLForm\FormElementCheckboxMultiple',
131
            // Address
132
        ];
133
134
        // $attributes['type'] must contain a valid type creating an object to succeed.
135
        $type = isset($attributes['type']) ? $attributes['type'] : null;
136
137
        if ($type && isset($types[$type])) {
138
            return new $types[$type]($name, $attributes);
139
        } else {
140
            throw new Exception("Form element does not exists and can not be created: $name - $type");
141
        }
142
    }
143
144
145
146
    /**
147
     * Get id of an element.
148
     *
149
     * @return HTML code for the element.
150
     */
151 9
    public function getElementId()
152
    {
153 9
        return ($this['id'] = isset($this['id']) ? $this['id'] : 'form-element-' . $this['name']);
154
    }
155
156
157
158
    /**
159
     * Get alll validation messages.
160
     *
161
     * @return HTML code for the element.
162
     */
163 9
    public function getValidationMessages()
164
    {
165 9
        $messages = null;
166 9
        if (isset($this['validation-messages'])) {
167
            $message = null;
168
            foreach ($this['validation-messages'] as $val) {
169
                $message .= "<li>{$val}</li>\n";
170
            }
171
            $messages = "<ul class='validation-message'>\n{$message}</ul>\n";
172
        }
173 9
        return $messages;
174
    }
175
176
177
178
    /**
179
     * Get details for a HTML element, prepare for creating HTML code for it.
180
     *
181
     * @return HTML code for the element.
182
     */
183 9
    public function getHTMLDetails()
184
    {
185
        // Add disabled to be able to disable a form element
186
        // Add maxlength
187 9
        $id =  $this->GetElementId();
188
189 9
        $class = isset($this['class'])
190 9
            ? "{$this['class']}"
191 9
            : null;
192
193 9
        $validates = (isset($this['validation-pass']) && $this['validation-pass'] === false)
194 9
            ? ' validation-failed'
195 9
            : null;
196
            
197 9
        $class = (isset($class) || isset($validates))
198 9
            ? " class='{$class}{$validates}'"
199 9
            : null;
200
            
201 9
        $name = " name='{$this['name']}'";
202
203 9
        $label = isset($this['label'])
204 9
            ? ($this['label'] . (isset($this['required']) && $this['required']
205 5
                ? "<span class='form-element-required'>*</span>"
206 5
                : null))
207 9
            : null;
208
            
209 9
        $autofocus = isset($this['autofocus']) && $this['autofocus']
210 9
            ? " autofocus='autofocus'"
211 9
            : null;
212
            
213 9
        $required = isset($this['required']) && $this['required']
214 9
            ? " required='required'"
215 9
            : null;
216
            
217 9
        $readonly = isset($this['readonly']) && $this['readonly']
218 9
            ? " readonly='readonly'"
219 9
            : null;
220
            
221 9
        $placeholder = isset($this['placeholder']) && $this['placeholder']
222 9
            ? " placeholder='{$this['placeholder']}'"
223 9
            : null;
224
            
225 9
        $multiple = isset($this['multiple']) && $this['multiple']
226 9
            ? " multiple"
227 9
            : null;
228
            
229 9
        $max = isset($this['max'])
230 9
            ? " max='{$this['max']}'"
231 9
            : null;
232
            
233 9
        $min = isset($this['min'])
234 9
            ? " min='{$this['min']}'"
235 9
            : null;
236
            
237 9
        $low = isset($this['low'])
238 9
            ? " low='{$this['low']}'"
239 9
            : null;
240
            
241 9
        $high = isset($this['high'])
242 9
            ? " high='{$this['high']}'"
243 9
            : null;
244
            
245 9
        $optimum = isset($this['optimum'])
246 9
            ? " optimum='{$this['optimum']}'"
247 9
            : null;
248
            
249 9
        $step = isset($this['step'])
250 9
            ? " step='{$this['step']}'"
251 9
            : null;
252
            
253 9
        $size = isset($this['size'])
254 9
            ? " size='{$this['size']}'"
255 9
            : null;
256
            
257 9
        $text = isset($this['text'])
258 9
            ? htmlentities($this['text'], ENT_QUOTES, $this->characterEncoding)
259 9
            : null;
260
            
261 9
        $checked = isset($this['checked']) && $this['checked']
262 9
            ? " checked='checked'"
263 9
            : null;
264
            
265 9
        $type = isset($this['type'])
266 9
            ? " type='{$this['type']}'"
267 9
            : null;
268
            
269 9
        $title = isset($this['title'])
270 9
            ? " title='{$this['title']}'"
271 9
            : null;
272
            
273 9
        $pattern = isset($this['pattern'])
274 9
            ? " pattern='{$this['pattern']}'"
275 9
            : null;
276
            
277 9
        $description = isset($this['description'])
278 9
            ? $this['description']
279 9
            : null;
280
281 9
        $novalidate = isset($this['formnovalidate'])
282 9
            ? " formnovalidate='formnovalidate'"
283 9
            : null;
284
285 9
        $onlyValue = isset($this['value'])
286 9
            ? htmlentities($this['value'], ENT_QUOTES, $this->characterEncoding)
287 9
            : null;
288
            
289 9
        $value = isset($this['value'])
290 9
            ? " value='{$onlyValue}'"
291 9
            : null;
292
293 9
        $messages = $this->getValidationMessages();
294
        
295
        return [
296 9
            'id'            => $id,
297 9
            'class'         => $class,
298 9
            'name'          => $name,
299 9
            'label'         => $label,
300 9
            'autofocus'     => $autofocus,
301 9
            'required'      => $required,
302 9
            'readonly'      => $readonly,
303 9
            'placeholder'   => $placeholder,
304 9
            'multiple'      => $multiple,
305 9
            'min'           => $min,
306 9
            'max'           => $max,
307 9
            'low'           => $low,
308 9
            'high'          => $high,
309 9
            'step'          => $step,
310 9
            'optimum'       => $optimum,
311 9
            'size'          => $size,
312 9
            'text'          => $text,
313 9
            'checked'       => $checked,
314 9
            'type'          => $type,
315 9
            'title'         => $title,
316 9
            'pattern'       => $pattern,
317 9
            'description'   => $description,
318 9
            'novalidate'    => $novalidate,
319 9
            'onlyValue'     => $onlyValue,
320 9
            'value'         => $value,
321 9
            'messages'      => $messages,
322 9
        ];
323
    }
324
325
326
327
    /**
328
     * Get HTML code for a element.
329
     *
330
     * TODO Move HTML generation to each specific element and make this method
331
     *      abstract.
332
     *
333
     * @return HTML code for the element.
334
     */
335 3
    public function getHTML()
336
    {
337 3
        $details = $this->getHTMLDetails();
338 3
        extract($details);
339
        
340
        // Create HTML for the element
341 3
        if (in_array($this['type'], ['submit', 'reset', 'button'])) {
342
            // type=submit || reset || button
343
            return <<<EOD
344
<span>
345 2
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$readonly}{$novalidate}{$title} />
346 2
</span>
347 2
EOD;
348 1
        } elseif ($this['type'] == 'search-widget') {
349
            // custom search-widget with type=search and type=submit
350
            $label = isset($this['label']) ? " value='{$this['label']}'" : null;
351
            $classSubmit = isset($this['class-submit']) ? " class='{$this['class-submit']}'" : null;
352
          
353
            return <<<EOD
354
<p>
355
<input id='$id' type='search'{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}/>
356
<input id='do-{$id}' type='submit'{$classSubmit}{$label}{$readonly}{$title}/>
357
</p>
358
<p class='cf-desc'>{$description}</p>
359
EOD;
360 1
        } elseif ($this['type'] == 'textarea') {
361
            // textarea
362
            return <<<EOD
363
<p>
364
<label for='$id'>$label</label><br/>
365
<textarea id='$id'{$class}{$name}{$autofocus}{$required}{$readonly}{$placeholder}{$title}>{$onlyValue}</textarea>
366
</p>
367
<p class='cf-desc'>{$description}</p>
368
EOD;
369 1
        } elseif ($this['type'] == 'hidden') {
370
            // type=hidden
371
            return "<input id='$id'{$type}{$class}{$name}{$value} />\n";
372 1
        } elseif ($this['type'] == 'radio') {
373
            // radio
374
            $ret = null;
375
            foreach ($this['values'] as $val) {
376
                $id .= $val;
377
                $item = $onlyValue  = htmlentities($val, ENT_QUOTES, $this->characterEncoding);
378
                $value = " value='{$onlyValue}'";
379
                $checked = isset($this['checked']) && $val === $this['checked']
380
                    ? " checked='checked'"
381
                    : null;
382
                $ret .= <<<EOD
383
<p>
384
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$readonly}{$checked}{$title} />
385
<label for='$id'>$item</label>
386
{$messages}
387
</p>
388
EOD;
389
            }
390
            return <<<EOD
391
<div>
392
<p class='cf-label'>{$label}</p>
393
{$ret}
394
<p class='cf-desc'>{$description}</p>
395
</div>
396
EOD;
397 1
        } elseif ($this['type'] == 'file-multiple') {
398
            // file-multiple
399
            // @codingStandardsIgnoreStart
400
            return <<<EOD
401
<p>
402
<label for='$id'>$label</label>
403
<br/>
404
<input id='$id' type='file' multiple{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}{$title}{$multiple}{$pattern}{$max}{$min}{$step}/>
405
{$messages}
406
</p>
407
<p class='cf-desc'>{$description}</p>
408
EOD;
409
            // @codingStandardsIgnoreEnd
410
        } else {
411
            // Everything else
412
            // @codingStandardsIgnoreStart
413
            return <<<EOD
414
<p>
415 1
<label for='$id'>$label</label>
416
<br/>
417 1
<input id='$id'{$type}{$class}{$name}{$value}{$autofocus}{$required}{$readonly}{$placeholder}{$title}{$multiple}{$pattern}{$max}{$min}{$step}/>
418 1
{$messages}
419
</p>
420 1
<p class='cf-desc'>{$description}</p>
421 1
EOD;
422
            // @codingStandardsIgnoreEnd
423
        }
424
    }
425
426
427
428
    /**
429
     * Validate the form element value according a ruleset.
430
     *
431
     * @param array     $rules validation rules.
432
     * @param Form|null $form  the parent form.
433
     *
434
     * @return boolean true if all rules pass, else false.
435
     */
436 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...
437
    {
438 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...
439
        $tests = [
440
            'fail' => [
441 2
                'message' => 'Will always fail.',
442
                'test' => 'return false;'
443 2
            ],
444
445
            'pass' => [
446 2
                'message' => 'Will always pass.',
447
                'test' => 'return true;'
448 2
            ],
449
450
            'not_empty' => [
451 2
                'message' => 'Can not be empty.',
452
                'test' => 'return $value != "";'
453 2
            ],
454
455
            'not_equal' => [
456 2
                'message' => 'Value not valid.',
457
                'test' => 'return $value != $arg;'
458 2
            ],
459
460
            'number' => [
461 2
                'message' => 'Must be a number.',
462
                'test' => 'return is_numeric($value);'
463 2
            ],
464
465
            'email' => [
466 2
                'message' => 'Must be an email adress.',
467 1
                'test' => function ($value) {
468 1
                    return preg_match('/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i', $value) === 1;
469
                }
470 2
            ],
471
472
            'match' => [
473 2
                'message' => 'The field does not match.',
474
                'test' => 'return $value == $form[$arg]["value"] ;'
475 2
            ],
476
              
477
            'must_accept' => [
478 2
                'message' => 'You must accept this.',
479
                'test' => 'return $checked;'
480 2
            ],
481
482 2
            'custom_test' => true,
483 2
        ];
484
485
        // max tecken, min tecken, datum, tid, datetime, mysql datetime
486
487 2
        $pass = true;
488 2
        $messages = array();
489 2
        $value = $this['value'];
490 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...
491
492 2
        foreach ($rules as $key => $val) {
493 2
            $rule = is_numeric($key) ? $val : $key;
494 2
            if (!isset($tests[$rule])) {
495 1
                throw new Exception("Validation of form element failed, no such validation rule exists: $rule");
496
            }
497 1
            $arg = is_numeric($key) ? null : $val;
498
499 1
            $test = ($rule == 'custom_test') ? $arg : $tests[$rule];
500 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...
501 1
            if (is_callable($test['test'])) {
502 1
                $status = $test['test']($value);
503 1
            } else {
504
                $status = eval($test['test']);
505
            }
506
507 1
            if ($status === false) {
508
                $messages[] = $test['message'];
509
                $pass = false;
510
            }
511 1
        }
512
513 1
        if (!empty($messages)) {
514
            $this['validation-messages'] = $messages;
515
        }
516 1
        return $pass;
517
    }
518
519
520
521
    /**
522
     * Use the element name as label if label is not set.
523
     *
524
     * @param string $append a colon as default to the end of the label.
525
     *
526
     * @return void
527
     */
528 10
    public function useNameAsDefaultLabel($append = ':')
529
    {
530 10 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...
531 10
            $this['label'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name']))).$append;
532 10
        }
533 10
    }
534
535
536
537
    /**
538
     * Use the element name as value if value is not set.
539
     *
540
     * @return void
541
     */
542 3
    public function useNameAsDefaultValue()
543
    {
544 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...
545 3
            $this['value'] = ucfirst(strtolower(str_replace(array('-','_'), ' ', $this['name'])));
546 3
        }
547 3
    }
548
549
550
551
    /**
552
     * Get the value of the form element.
553
     *
554
     * @return mixed the value of the form element.
555
     */
556 6
    public function getValue()
557
    {
558 6
        return $this['value'];
559
    }
560
561
562
563
    /**
564
     * Get the value of the form element.
565
     *
566
     * @return mixed the value of the form element.
567
     */
568
    public function value()
569
    {
570
        return $this['value'];
571
    }
572
573
574
575
    /**
576
     * Get the status of the form element if it is checked or not.
577
     *
578
     * @return mixed the value of the form element.
579
     */
580
    public function checked()
581
    {
582
        return $this['checked'];
583
    }
584
}
585