AbstractFormBuilder   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 0
Metric Value
wmc 44
lcom 2
cbo 8
dl 0
loc 325
rs 8.8798
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A getField() 0 12 2
A setField() 0 5 1
A getIdentifier() 0 4 1
A setFormAttributes() 0 4 1
A getFormOpen() 0 26 4
A getFormClose() 0 4 1
A setFormErrorContainer() 0 5 1
A setFormErrorItemContainer() 0 5 1
B getData() 0 28 6
A setData() 0 12 4
A isValid() 0 17 3
B add() 0 46 10
B _addValidators() 0 34 7
create() 0 1 ?

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Faulancer\Form;
4
5
use Faulancer\Exception\FormInvalidException;
6
use Faulancer\Exception\InvalidArgumentException;
7
use Faulancer\Exception\InvalidFormElementException;
8
use Faulancer\Exception\ServiceNotFoundException;
9
use Faulancer\Form\Type\AbstractType;
10
use Faulancer\Form\Validator\Base\Confirm;
11
use Faulancer\Http\Request;
12
use Faulancer\ORM\Entity;
13
use Faulancer\Service\Config;
14
use Faulancer\ServiceLocator\ServiceLocator;
15
use Faulancer\Form\Validator\ValidatorChain;
16
17
/**
18
 * Class AbstractFormBuilder
19
 *
20
 * @package Faulancer\Form\Type
21
 * @author Florian Knapp <[email protected]>
22
 */
23
abstract class AbstractFormBuilder
24
{
25
26
    /** @var string */
27
    protected $identifier = '';
28
29
    /** @var array */
30
    protected $formAttributes = [];
31
32
    /** @var AbstractType[] */
33
    protected $fields = [];
34
35
    /** @var Entity|null */
36
    protected $entity = null;
37
38
    /** @var string */
39
    protected $formErrorContainerPrefix = '';
40
41
    /** @var string */
42
    protected $formErrorContainerSuffix = '';
43
44
    /** @var string */
45
    protected $formErrorItemContainerPrefix = '';
46
47
    /** @var string */
48
    protected $formErrorItemContainerSuffix = '';
49
50
    /** @var string|null */
51
    private $confirmValue = null;
52
53
    /**
54
     * AbstractFormBuilder constructor
55
     * @param Entity $entity
56
     * @codeCoverageIgnore
57
     */
58
    public function __construct(Entity $entity = null)
59
    {
60
        $this->create();
61
62
        if ($entity !== null) {
63
            $this->setData($entity->getDataAsArray());
64
        }
65
    }
66
67
    /**
68
     * Get field object
69
     *
70
     * @param string $name
71
     *
72
     * @return AbstractType
73
     *
74
     * @throws InvalidFormElementException
75
     */
76
    public function getField(string $name)
77
    {
78
        if (empty($this->fields[$name])) {
79
80
            $trace = \debug_backtrace();
81
            $first = array_shift($trace);
82
83
            throw new InvalidFormElementException('No field with name "' . $name . '" found', 0, $first['line'], $first['file']);
84
        }
85
86
        return $this->fields[$name];
87
    }
88
89
    /**
90
     * @param AbstractType $field
91
     *
92
     * @return self
93
     */
94
    public function setField(AbstractType $field)
95
    {
96
        $this->fields[$field->getName()] = $field;
97
        return $this;
98
    }
99
100
    /**
101
     * @return string
102
     */
103
    public function getIdentifier()
104
    {
105
        return $this->identifier;
106
    }
107
108
    /**
109
     * @param array $attributes
110
     */
111
    public function setFormAttributes(array $attributes)
112
    {
113
        $this->formAttributes = $attributes;
114
    }
115
116
    /**
117
     * @return string
118
     */
119
    public function getFormOpen()
120
    {
121
        $unknownAttributes = '';
122
        $knownAttributes   = ['action', 'method', 'enctype', 'autocomplete'];
123
124
        $action       = $this->formAttributes['action'] ?? '';
125
        $method       = $this->formAttributes['method'] ?? '';
126
        $enctype      = $this->formAttributes['enctype'] ?? 'application/x-www-form-urlencoded';
127
        $autocomplete = $this->formAttributes['autocomplete'] ?? 'on';
128
129
        foreach ($this->formAttributes as $attr => $value) {
130
131
            if (in_array($attr, $knownAttributes)) {
132
                continue;
133
            }
134
135
            $unknownAttributes .= ' ' . $attr . '="' . $value . '" ';
136
137
        }
138
139
        if ($unknownAttributes) {
140
            $unknownAttributes = substr($unknownAttributes, 0, strlen($unknownAttributes) - 1);
141
        }
142
143
        return '<form action="' . $action . '" method="' . $method . '" enctype="' . $enctype . '" autocomplete="' . $autocomplete . '"' . $unknownAttributes . '>';
144
    }
145
146
    /**
147
     * @return string
148
     */
149
    public function getFormClose()
150
    {
151
        return '</form>';
152
    }
153
154
    /**
155
     * @param string $prefix
156
     * @param string $suffix
157
     */
158
    public function setFormErrorContainer(string $prefix, string $suffix)
159
    {
160
        $this->formErrorContainerPrefix = $prefix;
161
        $this->formErrorContainerSuffix = $suffix;
162
    }
163
164
    /**
165
     * @param string $prefix
166
     * @param string $suffix
167
     */
168
    public function setFormErrorItemContainer(string $prefix, string $suffix)
169
    {
170
        $this->formErrorItemContainerPrefix = $prefix;
171
        $this->formErrorItemContainerSuffix = $suffix;
172
    }
173
174
    /**
175
     * @return array
176
     */
177
    public function getData()
178
    {
179
        $result = [];
180
181
        /** @var AbstractType $value */
182
        foreach ($this->fields as $key => $field) {
183
184
            if ($field->getType() === 'submit' || empty($field->getValue())) {
185
                continue;
186
            }
187
188
            if (in_array(Confirm::class, $field->getValidatorChain()->getValidators())) {
189
190
                // Check stored confirm value (i.e. for password repeat requests) and ignore the second field
191
                if ($field->getValue() === $this->confirmValue) {
192
                    continue;
193
                }
194
195
                $this->confirmValue = $field->getValue();
196
197
            }
198
199
            $result[$key] = $field->getValue();
200
201
        }
202
203
        return $result;
204
    }
205
206
    /**
207
     * @param array $data
208
     */
209
    public function setData(array $data)
210
    {
211
        foreach ($data as $key => $value) {
212
213
            if (empty($this->fields[$key]) || $this->fields[$key]->getType() === 'password') {
214
                continue;
215
            }
216
217
            $this->fields[$key]->setValue($value);
218
219
        }
220
    }
221
222
    /**
223
     * @return bool
224
     */
225
    public function isValid() :bool
226
    {
227
        $errors = [];
228
229
        /** @var AbstractType $field */
230
        foreach ($this->fields as $field) {
231
232
            $result = $field->isValid();
233
234
            if ($result !== null) {
235
                $errors[] = $result;
236
            }
237
238
        }
239
240
        return !in_array(false, $errors, true);
241
    }
242
243
    /**
244
     * Add a form field
245
     *
246
     * @param array $definition
247
     *
248
     * @throws InvalidArgumentException
249
     * @throws ServiceNotFoundException
250
     * @throws FormInvalidException
251
     */
252
    public function add(array $definition)
253
    {
254
        $type = $definition['attributes']['type'];
255
        $name = $definition['attributes']['name'];
256
257
        $namespace = '\Faulancer\Form\Type\Base\\' . ucfirst($type);
258
259
        if (!class_exists($namespace)) {
260
            throw new InvalidArgumentException('Requesting non existent form type ' . ucfirst($type));
261
        }
262
263
        $formErrorDecoration = [
264
            'containerPrefix'     => $this->formErrorContainerPrefix,
265
            'containerSuffix'     => $this->formErrorContainerSuffix,
266
            'containerItemPrefix' => $this->formErrorItemContainerPrefix,
267
            'containerItemSuffix' => $this->formErrorItemContainerSuffix
268
        ];
269
270
        /** @var AbstractType $typeClass */
271
        $typeClass = new $namespace($definition, $formErrorDecoration, $this->identifier);
272
273
        $typeClass->setName($name);
274
        $typeClass->setType($type);
275
276
        if (!empty($this->fields[$name]) && !empty($this->fields[$name]->getValue())) {
277
            $typeClass->setValue($this->fields[$name]->getValue());
278
        }
279
280
        /** @var Request $request */
281
        $request  = ServiceLocator::instance()->get(Request::class);
282
        $postData = $request->getPostData();
283
284
        if ($request->isPost() && !empty($postData[$name])) {
285
            $typeClass->setValue($postData[$name]);
286
        }
287
288
        // If radio or checkbox field isn't selected, the field wouldn't
289
        // be send within post data so always add a validator if exists
290
        $isRadioOrCheckbox = $type === 'radio' || $type === 'checkbox';
291
292
        if ($isRadioOrCheckbox || ($request->isPost() && in_array($name, array_keys($postData)))) {
293
            $this->_addValidators($typeClass, $definition);
294
        }
295
296
        $this->fields[$name] = $typeClass->create();
297
    }
298
299
    /**
300
     * @param AbstractType $typeClass
301
     * @param array        $definition
302
     *
303
     * @return bool
304
     *
305
     * @throws FormInvalidException
306
     */
307
    private function _addValidators(AbstractType &$typeClass, array $definition)
308
    {
309
        if (!empty($definition['validator'])) {
310
311
            if ($definition['validator'] === 'none') {
312
                return true;
313
            }
314
315
            $validatorChain = new ValidatorChain($typeClass);
316
317
            foreach ($definition['validator'] as $validator) {
318
319
                if (!class_exists($validator)) {
320
                    throw new FormInvalidException('Validator "' . $validator . '" doesn\'t exists');
321
                }
322
323
                $validatorChain->add(new $validator($typeClass));
324
325
            }
326
327
            $typeClass->setValidatorChain($validatorChain);
328
329
        } else {
330
331
            $validator = '\Faulancer\Form\Validator\Base\\' . ucfirst($typeClass->getType());
332
333
            if (empty($definition['validator']) && class_exists($validator)) {
334
                $typeClass->setDefaultValidator(new $validator($typeClass));
335
            }
336
337
        }
338
339
        return true;
340
    }
341
342
    /**
343
     * @return mixed
344
     */
345
    abstract protected function create();
346
347
}