Completed
Push — master ( 8813b2...5b4b97 )
by Flo
04:17
created

AbstractFormBuilder::getFormOpen()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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