AbstractForm::isEmpty()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 1
nc 5
nop 1
dl 0
loc 3
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
namespace WebComplete\form;
4
5
abstract class AbstractForm implements FormInterface
6
{
7
    use TraitErrors;
8
9
    const REQUIRED = 'required';
10
11
    protected $data = [];
12
    protected $defaultError = 'error';
13
    protected $filtersObject;
14
    protected $validatorsObject;
15
    private $rules;
16
    private $filters;
17
18
    /**
19
     * AbstractForm constructor.
20
     *
21
     * @param null|array $rules
22
     * @param null|array $filters
23
     * @param $filtersObject
24
     * @param $validatorsObject
25
     */
26
    public function __construct($rules = null, $filters = null, $validatorsObject = null, $filtersObject = null)
27
    {
28
        if (\is_object($filtersObject)) {
29
            $this->filtersObject = $filtersObject;
30
        }
31
        if (\is_object($validatorsObject)) {
32
            $this->validatorsObject = $validatorsObject;
33
        }
34
35
        $this->rules = \is_array($rules) ? \array_merge($this->rules(), $rules) : $this->rules();
36
37
        $this->filters = \is_array($filters) ? \array_merge($this->filters(), $filters) : $this->filters();
38
    }
39
40
    /**
41
     * @return bool
42
     * @throws \WebComplete\form\FormException
43
     */
44
    public function validate(): bool
45
    {
46
        /** @var array[] $definitions */
47
        $definitions = $this->normalize($this->rules);
48
49
        $this->resetErrors();
50
        $this->validateRequired($definitions);
51
52
        foreach ($definitions as $field => $fieldDefinitions) {
53
            $value = $this->getValue($field);
54
            foreach ($fieldDefinitions as $definition) {
55
                $defName = \array_shift($definition);
56
                $defParams = \array_merge([$value], [\array_shift($definition)], [$this]);
57
                $defMessage = \array_shift($definition) ?: $this->defaultError;
58
59
                if ($defName !== self::REQUIRED && !$this->isEmpty($value)
60
                    && !$this->call($defName, $defParams, $this->validatorsObject, true)) {
61
                    $this->addError($field, $defMessage);
62
                }
63
            }
64
        }
65
66
        return !$this->hasErrors();
67
    }
68
69
    /**
70
     * @return array
71
     */
72
    public function getData(): array
73
    {
74
        return $this->data;
75
    }
76
77
    /**
78
     * @param array $data
79
     *
80
     * @return $this
81
     * @throws \WebComplete\form\FormException
82
     */
83
    public function setData(array $data)
84
    {
85
        $this->data = $this->filter($data);
86
87
        return $this;
88
    }
89
90
    /**
91
     * @param $field
92
     *
93
     * @return mixed|null
94
     */
95
    public function getValue($field)
96
    {
97
        return $this->getDataValue($this->data, $field);
98
    }
99
100
    /**
101
     * @param $field
102
     * @param $value
103
     * @param bool $filter
104
     *
105
     * @throws \WebComplete\form\FormException
106
     */
107
    public function setValue($field, $value, $filter = true)
108
    {
109
        if ($filter) {
110
            $data = $this->filter([$field => $value]);
111
            $value = $data[$field] ?? null;
112
        }
113
        $this->setDataValue($this->data, $field, $value);
114
    }
115
116
    /**
117
     * @param array $data
118
     *
119
     * @return array
120
     * @throws \WebComplete\form\FormException
121
     */
122
    protected function filter(array $data): array
123
    {
124
        $filtersDefinitions = $this->normalize($this->filters);
125
        $rulesDefinitions = $this->normalize($this->rules);
126
127
        foreach ($data as $field => $value) {
128
            if (!$this->hasKey($rulesDefinitions, $field) && !$this->hasKey($filtersDefinitions, $field)) {
129
                unset($data[$field]);
130
                continue;
131
            }
132
        }
133
134
        if (isset($filtersDefinitions['*'])) {
135
            foreach ($filtersDefinitions as $field => $fieldDefinitions) {
136
                /** @noinspection SlowArrayOperationsInLoopInspection */
137
                $filtersDefinitions[$field] = \array_merge($fieldDefinitions, $filtersDefinitions['*']);
138
            }
139
            foreach (\array_keys($rulesDefinitions) as $field) {
140
                if ($field !== '*' && !isset($filtersDefinitions[$field])) {
141
                    $filtersDefinitions[$field] = $filtersDefinitions['*'];
142
                }
143
            }
144
            unset($filtersDefinitions['*']);
145
        }
146
147
        foreach ($filtersDefinitions as $field => $fieldDefinitions) {
148
            $value = $this->getDataValue($data, $field);
149
            foreach ((array)$fieldDefinitions as $definition) {
150
                $defName = \array_shift($definition);
151
                $defParams = \array_merge([$value], [\array_shift($definition)], [$this]);
152
                $value = $this->call($defName, $defParams, $this->filtersObject, $value);
153
            }
154
            $this->setDataValue($data, $field, $value);
155
        }
156
157
        return $data;
158
    }
159
160
    /**
161
     * @param $value
162
     *
163
     * @return bool
164
     */
165
    protected function isEmpty($value): bool
166
    {
167
        return $value === null || $value === '' || (\is_array($value) && !\count($value));
168
    }
169
170
    /**
171
     * @param $definitions
172
     */
173
    protected function validateRequired($definitions)
174
    {
175
        /** @var array[] $definitions */
176
        foreach ($definitions as $field => $fieldDefinitions) {
177
            foreach ($fieldDefinitions as $definition) {
178
                if ($definition[0] === self::REQUIRED && $this->isEmpty($this->getValue($field))) {
179
                    $this->addError($field, !empty($definition[2]) ? $definition[2] : $this->defaultError);
180
                }
181
            }
182
        }
183
    }
184
185
    /**
186
     * @param $definitions
187
     *
188
     * @return array
189
     */
190
    private function normalize($definitions): array
191
    {
192
        $normalized = [];
193
        /** @var array[] $definitions */
194
        foreach ($definitions as $definition) {
195
            $fields = \array_shift($definition);
196
            $defName = $definition ? \array_shift($definition) : null;
197
            $defParams = $definition ? \array_shift($definition) : [];
198
            $defMessage = $definition ? \array_shift($definition) : '';
199
            $fields = (array)$fields;
200
            foreach ($fields as $field) {
201
                if (!isset($normalized[$field])) {
202
                    $normalized[$field] = [];
203
                }
204
                $normalized[$field][] = [$defName, $defParams, $defMessage];
205
            }
206
        }
207
208
        return $normalized;
209
    }/** @noinspection MoreThanThreeArgumentsInspection */
210
211
    /**
212
     * @param $defName
213
     * @param $defParams
214
     * @param $object
215
     * @param $default
216
     *
217
     * @return mixed|null
218
     * @throws FormException
219
     */
220
    private function call($defName, $defParams, $object, $default)
221
    {
222
        $callable = $defName;
223
        if ($defName) {
224
            if (\is_string($defName)) {
225
                if (\method_exists($this, $defName)) {
226
                    $callable = [$this, $defName];
227
                } elseif ($object && \method_exists($object, $defName)) {
228
                    $callable = [$object, $defName];
229
                }
230
            }
231
232
            if (!\is_callable($callable)) {
233
                throw new FormException('Callable not found: ' . \json_encode($callable));
234
            }
235
        }
236
237
        return $callable ? \call_user_func_array($callable, $defParams) : $default;
238
    }
239
240
    /**
241
     * @param array $array
242
     * @param string $key
243
     *
244
     * @return bool
245
     */
246
    private function hasKey(array $array, string $key): bool
247
    {
248
        $keys = \array_keys($array);
249
        foreach ($keys as $arrayKey) {
250
            if ($arrayKey === $key || \strpos($arrayKey, $key . '.', 0) === 0) {
251
                return true;
252
            }
253
        }
254
        return false;
255
    }
256
257
    /**
258
     * @param $array
259
     * @param $path
260
     *
261
     * @return mixed|null
262
     */
263
    private function getDataValue($array, $path)
264
    {
265
        $array = (array)$array;
266
        if (\is_array($array) && (isset($array[$path]) || \array_key_exists($path, $array))) {
267
            return $array[$path];
268
        }
269
270
        $pos = \strrpos($path, '.');
271
        if ($pos !== false) {
0 ignored issues
show
introduced by
The condition $pos !== false can never be false.
Loading history...
272
            $array = $this->getDataValue($array, \substr($path, 0, $pos));
273
            $path = (string)\substr($path, $pos + 1);
274
        }
275
276
        if (\is_array($array)) {
277
            return (isset($array[$path]) || \array_key_exists($path, $array)) ? $array[$path] : null;
278
        }
279
280
        return null;
281
    }
282
283
    /**
284
     * @param array $array
285
     * @param $path
286
     * @param $value
287
     */
288
    private function setDataValue(array &$array, $path, $value)
289
    {
290
        if ($path === null) {
291
            $array = $value;
292
            return;
293
        }
294
295
        $keys = \is_array($path) ? $path : \explode('.', $path);
296
297
        while (\count($keys) > 1) {
298
            $key = \array_shift($keys);
299
            if (!isset($array[$key])) {
300
                $array[$key] = [];
301
            }
302
            if (!\is_array($array[$key])) {
303
                $array[$key] = [$array[$key]];
304
            }
305
            $array = &$array[$key];
306
        }
307
308
        $array[\array_shift($keys)] = $value;
309
    }
310
}
311