Completed
Push — master ( 36f978...ecfa1c )
by Shcherbak
02:51
created

Form   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 465
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 100%

Importance

Changes 20
Bugs 7 Features 3
Metric Value
wmc 53
c 20
b 7
f 3
lcom 1
cbo 14
dl 0
loc 465
ccs 109
cts 109
cp 1
rs 7.4757

27 Methods

Rating   Name   Duplication   Size   Complexity  
A setName() 0 5 1
A handle() 0 14 4
A getMethod() 0 7 2
A setMethod() 0 5 1
A cleanValidationFlag() 0 4 1
D isValid() 0 34 9
A getErrors() 0 3 1
A addError() 0 8 2
A isSubmitted() 0 3 1
A getUid() 0 7 2
A getElements() 0 3 1
A getElement() 0 6 2
A setElement() 0 5 1
A addElement() 0 9 2
A input() 0 7 1
A password() 0 7 1
A select() 0 7 1
A radioList() 0 7 1
A textarea() 0 9 2
A hidden() 0 9 1
A submit() 0 7 1
A checkbox() 0 6 1
A checkboxList() 0 7 1
A render() 0 3 1
B renderElements() 0 27 6
B renderStart() 0 31 5
A renderEnd() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Form 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 Form, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
  namespace Fiv\Form;
4
5
  use Fiv\Form\Element;
6
  use Fiv\Form\Element\Checkbox;
7
  use Fiv\Form\Element\CheckboxList;
8
  use Fiv\Form\Element\ElementInterface;
9
  use Fiv\Form\Element\RadioList;
10
  use Fiv\Form\Element\Select;
11
  use Fiv\Form\Element\Submit;
12
  use Fiv\Form\Element\TextArea;
13
  use Fiv\Form\Elements\DataElementInterface;
14
  use Fiv\Form\Elements\ValidatableElementInterface;
15
16
  /**
17
   * @author Ivan Shcherbak <[email protected]>
18
   */
19
  class Form extends Element\Html {
20
21
    /**
22
     * @var null|string
23
     */
24
    protected $uid = null;
25
26
    /**
27
     * @var boolean|null
28
     */
29
    protected $validationResult = null;
30
31
    /**
32
     * @var array
33
     */
34
    protected $errorList = [];
35
36
    /**
37
     * @var bool
38
     */
39
    protected $isSubmitted = false;
40
41
    /**
42
     * @var DataElementInterface[]
43
     */
44
    protected $elements = [];
45
46
    /**
47
     * Default form attributes
48
     *
49
     * @var array
50
     */
51
    protected $attributes = [
52
      'method' => 'post',
53
    ];
54
55
56
    /**
57
     * @param $name
58
     * @return $this
59
     */
60 14
    public function setName($name) {
61 14
      $this->uid = $name;
62 14
      $this->attributes['name'] = $name;
63 14
      return $this;
64
    }
65
66
67
    /**
68
     * @param FormData $data
69
     * @return $this
70
     */
71 27
    public function handle(FormData $data) {
72 27
      $this->cleanValidationFlag();
73
74 27
      $this->isSubmitted = false;
75 27
      if ($data->isMethod($this->getMethod()) and $data->has($this->getUid())) {
76 26
        $this->isSubmitted = true;
77 26
        foreach ($this->getElements() as $element) {
78 25
          $element->handle($data);
79
        }
80
81
      }
82
83 27
      return $this;
84
    }
85
86
87
    /**
88
     * @return string
89
     */
90 31
    public function getMethod() {
91 31
      if (!empty($this->attributes['method'])) {
92 31
        return strtolower($this->attributes['method']);
93
      }
94
95 1
      return null;
96
    }
97
98
99
    /**
100
     * @param string $method
101
     * @return $this
102
     */
103 3
    public function setMethod($method) {
104 3
      $this->attributes['method'] = $method;
105
106 3
      return $this;
107
    }
108
109
110
    /**
111
     *
112
     */
113 32
    protected function cleanValidationFlag() {
114 32
      $this->errorList = [];
115 32
      $this->validationResult = null;
116 32
    }
117
118
119
    /**
120
     * Check if form is submitted and all elements are valid
121
     *
122
     * @return boolean
123
     */
124 14
    public function isValid() {
125 14
      if ($this->validationResult !== null) {
126 1
        return $this->validationResult;
127
      }
128
129 14
      if (!$this->isSubmitted()) {
130 2
        return false;
131
      }
132
133 13
      $this->validationResult = true;
134
135 13
      foreach ($this->getElements() as $element) {
136 12
        if ($element instanceof ValidatableElementInterface) {
137
          $validationResult = $element->validate();
138
          foreach ($validationResult->getErrors() as $errorMessage) {
139
            $this->addError($errorMessage);
140
          }
141
        }
142
143 12
        if (!$element instanceof ElementInterface) {
144 1
          continue;
145
        }
146
147 11
        if ($element->isValid()) {
148 11
          continue;
149
        }
150
151 6
        foreach ($element->getValidatorsErrors() as $errorMessage) {
152 6
          $this->addError($errorMessage);
153
        }
154
      }
155
156 13
      return $this->validationResult;
157
    }
158
159
160
    /**
161
     * @return array
162
     */
163 2
    public function getErrors() {
164 2
      return $this->errorList;
165
    }
166
167
168
    /**
169
     * @param string $error
170
     * @return $this
171
     */
172 7
    protected function addError($error) {
173 7
      if (!is_string($error)) {
174
        throw new \InvalidArgumentException('Error should be a string, ' . gettype($error) . ' given.');
175
      }
176 7
      $this->validationResult = false;
177 7
      $this->errorList[] = $error;
178 7
      return $this;
179
    }
180
181
182
    /**
183
     * Check if form is submitted
184
     *
185
     * @return bool
186
     */
187 17
    public function isSubmitted() {
188 17
      return $this->isSubmitted;
189
    }
190
191
192
    /**
193
     * Return unique id of form
194
     *
195
     * @return string
196
     */
197 31
    public function getUid() {
198 31
      if (empty($this->uid)) {
199 20
        $this->uid = md5(get_called_class());
200
      }
201
202 31
      return $this->uid;
203
    }
204
205
206
    /**
207
     * @return DataElementInterface[]
208
     */
209 32
    public function getElements() {
210 32
      return $this->elements;
211
    }
212
213
214
    /**
215
     * @param string $name
216
     * @return DataElementInterface
217
     * @throws \InvalidArgumentException
218
     */
219 2
    public function getElement($name) {
220 2
      if (empty($this->elements[$name])) {
221 1
        throw new \InvalidArgumentException('Element with name "' . $name . '" not found');
222
      }
223 1
      return $this->elements[$name];
224
    }
225
226
227
    /**
228
     * Attach element to this form. Overwrite element with same name
229
     *
230
     * @param DataElementInterface $element
231
     * @return $this
232
     */
233 2
    public function setElement(DataElementInterface $element) {
234 2
      $this->cleanValidationFlag();
235 2
      $this->elements[$element->getName()] = $element;
236 2
      return $this;
237
    }
238
239
240
    /**
241
     * @param DataElementInterface $element
242
     * @return $this
243
     * @throws \Exception
244
     */
245 28
    public function addElement(DataElementInterface $element) {
246 28
      if (isset($this->elements[$element->getName()])) {
247 1
        throw new \Exception('Element with name ' . $element->getName() . ' is already added. Use setElement to overwrite it or change name');
248
      }
249
250 28
      $this->cleanValidationFlag();
251 28
      $this->elements[$element->getName()] = $element;
252 28
      return $this;
253
    }
254
255
256
    /**
257
     * @param string $name
258
     * @param string|null $text
259
     * @return \Fiv\Form\Element\Input
260
     */
261 12
    public function input($name, $text = null) {
262 12
      $input = new Element\Input();
263 12
      $input->setName($name);
264 12
      $input->setText($text);
265 12
      $this->addElement($input);
266 12
      return $input;
267
    }
268
269
270
    /**
271
     * @param string $name
272
     * @param string|null $text
273
     * @return \Fiv\Form\Element\Input
274
     */
275
    public function password($name, $text = null) {
276
      $input = new Element\Password();
277
      $input->setName($name);
278
      $input->setText($text);
279
      $this->addElement($input);
280
      return $input;
281
    }
282
283
284
    /**
285
     * @param string $name
286
     * @param null $text
287
     * @return Select
288
     */
289
    public function select($name, $text = null) {
290
      $select = new Select();
291
      $select->setName($name);
292
      $select->setText($text);
293
      $this->addElement($select);
294
      return $select;
295
    }
296
297
298
    /**
299
     * @param string $name
300
     * @param string $text
301
     * @return RadioList
302
     */
303
    public function radioList($name, $text = null) {
304
      $radio = new RadioList();
305
      $radio->setName($name);
306
      $radio->setText($text);
307
      $this->addElement($radio);
308
      return $radio;
309
    }
310
311
312
    /**
313
     * @deprecated
314
     * @see TextArea
315
     *
316
     * @param string $name
317
     * @param null $text
318
     * @return TextArea
319
     */
320
    public function textarea($name, $text = null) {
321
      trigger_error('Deprecated. Create new element and add it to the form manually', E_USER_DEPRECATED);
322
      $input = new TextArea($name);
323
      if (!empty($text)) {
324
        $input->setText((string) $text);
325
      }
326
      $this->addElement($input);
327
      return $input;
328
    }
329
330
331
    /**
332
     * @deprecated
333
     * @see Element\Hidden
334
     *
335
     * @param string $name
336
     * @param null $value
337
     * @return \Fiv\Form\Element\Input
338
     */
339
    public function hidden($name, $value = null) {
340
      trigger_error('Deprecated. use ' . Element\Hidden::class . ' class', E_USER_WARNING);
341
      $hidden = new  \Fiv\Form\Element\Input();
342
      $hidden->setType('hidden');
343
      $hidden->setName($name);
344
      $hidden->setValue($value);
345
      $this->addElement($hidden);
346
      return $hidden;
347
    }
348
349
350
    /**
351
     * ```
352
     * $form->submit('register', 'зареєструватись');
353
     * ```
354
     * @param string $name
355
     * @param null $value
356
     * @return Submit
357
     */
358 3
    public function submit($name, $value = null) {
359 3
      $input = new Submit();
360 3
      $input->setName($name);
361 3
      $input->setValue($value);
362 3
      $this->addElement($input);
363 3
      return $input;
364
    }
365
366
367
    /**
368
     * ```
369
     * $form->checkbox('subscribe', 'Підписка на новини');
370
     * ```
371
     * @param string $name
372
     * @param string|null $label
373
     * @return Checkbox
374
     */
375
    public function checkbox($name, $label = null) {
376
      trigger_error('Deprecated', E_USER_DEPRECATED);
377
      $checkbox = new Checkbox($name, $label);
378
      $this->addElement($checkbox);
379
      return $checkbox;
380
    }
381
382
383
    /**
384
     * @param string $name
385
     * @param null $text
386
     * @return CheckboxList
387
     */
388
    public function checkboxList($name, $text = null) {
389
      $checkbox = new CheckboxList();
390
      $checkbox->setName($name);
391
      $checkbox->setText($text);
392
      $this->addElement($checkbox);
393
      return $checkbox;
394
    }
395
396
397
    /**
398
     * Render full form
399
     *
400
     * @return string
401
     */
402 2
    public function render() {
403 2
      return $this->renderStart() . $this->renderElements() . $this->renderEnd();
404
    }
405
406
407
    /**
408
     * You can easy rewrite this method for custom design of your forms
409
     *
410
     * @return string
411
     */
412 2
    protected function renderElements() {
413 2
      $formHtml = '<dl>';
414
415 2
      foreach ($this->getElements() as $element) {
416
        # skip hidden element
417 1
        if ($element instanceof Element\Hidden) {
418
          continue;
419
        }
420
421 1
        if ($element instanceof Element\Input and $element->getType() === 'hidden') {
422
          continue;
423
        }
424
425 1
        $elementText = '';
426
427 1
        if ($element instanceof Element\BaseElement) {
428
          $elementText = $element->getText();
429
        }
430
431
        $formHtml .=
432 1
          '<dt>' . $elementText . '</dt>' .
433 1
          '<dd>' . $element->render() . '</dd>';
434
      }
435
436 2
      $formHtml .= '</dl>';
437 2
      return $formHtml;
438
    }
439
440
441
    /**
442
     * @return string
443
     */
444 3
    public function renderStart() {
445 3
      $hidden = new Element\Input();
446 3
      $hidden->setType('hidden');
447 3
      $hidden->addAttributes([
448 3
        'name' => $this->getUid(),
449
      ]);
450 3
      $hidden->setValue(1);
451
452
453
      # get default attribute
454 3
      $method = $this->getMethod();
455 3
      $this->setAttribute('method', $method);
456
457 3
      $html = '<form ' . Element\Html::renderAttributes($this->getAttributes()) . '>';
458 3
      $html .= $hidden->render();
459
460
      # render hidden element
461 3
      foreach ($this->getElements() as $element) {
462 2
        if ($element instanceof Element\Input and $element->getType() === 'hidden') {
463
          $html .= $element->render();
464
        }
465
466 2
        if ($element instanceof Element\Hidden) {
467 2
          $html .= $element->render();
468
        }
469
470
      }
471
472
473 3
      return $html;
474
    }
475
476
477
    /**
478
     * @return string
479
     */
480 3
    public function renderEnd() {
481 3
      return '</form>';
482
    }
483
  }