Test Failed
Pull Request — master (#19)
by Flo
02:36
created

AbstractFormBuilder   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Importance

Changes 0
Metric Value
wmc 40
lcom 2
cbo 8
dl 0
loc 292
rs 8.2608
c 0
b 0
f 0

15 Methods

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