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

AbstractFormBuilder::add()   D

Complexity

Conditions 10
Paths 17

Size

Total Lines 46
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 46
rs 4.983
c 0
b 0
f 0
cc 10
eloc 24
nc 17
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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