Validator   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 89
c 2
b 0
f 0
dl 0
loc 382
rs 9.0399
wmc 42

21 Methods

Rating   Name   Duplication   Size   Complexity  
A validateRule() 0 8 2
A getLabel() 0 3 1
A addRules() 0 7 2
A setLabel() 0 5 1
A validateRules() 0 15 4
A addFilters() 0 7 2
A validate() 0 11 2
A getData() 0 6 3
A applyFilters() 0 22 5
A getRules() 0 5 3
A validateFieldRules() 0 11 3
A getLang() 0 3 1
A __construct() 0 9 1
A translate() 0 7 2
A reset() 0 10 1
A addRule() 0 13 3
A setData() 0 4 1
A getErrors() 0 3 1
A addFilter() 0 9 2
A isValid() 0 3 1
A humanizeFieldName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Validator 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.

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 Validator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Platine Validator
5
 *
6
 * Platine Validator is a simple, extensible validation library with support for filtering
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Validator
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file Validator.php
33
 *
34
 *  The Validator class used to validate the input based on defined rules
35
 *
36
 *  @package    Platine\Validator
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   https://www.platine-php.com
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Validator;
48
49
use Platine\Lang\Lang;
50
51
/**
52
 * @class Validator
53
 * @package Platine\Validator
54
 *
55
 * A Validator contains a set of validation rules and
56
 * associated metadata for ensuring that a given data set
57
 * is valid and returned correctly.
58
 */
59
class Validator
60
{
61
    /**
62
     * The data to validate
63
     * @var array<string, mixed>
64
     */
65
    protected array $data = [];
66
67
    /**
68
     * The field labels
69
     * @var array<string, string>
70
     */
71
    protected array $labels = [];
72
73
    /**
74
     * The filters to use to filter validation data
75
     * @var array<string, callable[]>
76
     */
77
    protected array $filters = [];
78
79
    /**
80
     * The validate rules
81
     * @var array<string, RuleInterface[]>
82
     */
83
    protected array $rules = [];
84
85
    /**
86
     * The validate errors
87
     * @var array<string, string>
88
     */
89
    protected array $errors = [];
90
91
    /**
92
     * The status of the validation
93
     * @var bool
94
     */
95
    protected bool $valid = false;
96
97
    /**
98
     * The validation language domain to use
99
     * @var string
100
     */
101
    protected string $langDomain;
102
103
    /**
104
     * The language to use
105
     * @var Lang
106
     */
107
    protected Lang $lang;
108
109
110
    /**
111
     * Create new instance
112
     * @param Lang $lang
113
     * @param string $langDomain
114
     */
115
    public function __construct(Lang $lang, string $langDomain = 'validators')
116
    {
117
        $this->lang = $lang;
118
        $this->langDomain = $langDomain;
119
120
        //Add the domain for the validator
121
        $this->lang->addDomain($langDomain);
122
123
        $this->reset();
124
    }
125
126
    /**
127
     * Return the language instance
128
     * @return Lang
129
     */
130
    public function getLang(): Lang
131
    {
132
        return $this->lang;
133
    }
134
135
    /**
136
     * Translation a single message
137
     * @param string $message
138
     * @param mixed $args
139
     * @return string
140
     */
141
    public function translate(string $message, mixed $args = []): string
142
    {
143
        if (!is_array($args)) {
144
            $args = array_slice(func_get_args(), 1);
145
        }
146
147
        return $this->lang->trd($message, $this->langDomain, $args);
148
    }
149
150
    /**
151
     * Reset the validation instance
152
     *
153
     * @return $this
154
     */
155
    public function reset(): self
156
    {
157
        $this->rules = [];
158
        $this->labels = [];
159
        $this->errors = [];
160
        $this->filters = [];
161
        $this->valid = false;
162
        $this->data = [];
163
164
        return $this;
165
    }
166
167
    /**
168
     * Set the validation data
169
     * @param array<string, mixed> $data the data to be validated
170
     *
171
     * @return $this
172
     */
173
    public function setData(array $data): self
174
    {
175
        $this->data = $data;
176
        return $this;
177
    }
178
179
    /**
180
     * Return the validation data
181
     *
182
     * @param string $field if is set will return only the data for this filed
183
     * @param mixed $default the default value to return if can not find field value
184
     * @return mixed
185
     */
186
    public function getData(?string $field = null, mixed $default = null): mixed
187
    {
188
        if ($field === null) {
189
            return $this->data;
190
        }
191
        return array_key_exists($field, $this->data) ? $this->data[$field] : $default;
192
    }
193
194
    /**
195
     * Return the validation status
196
     *
197
     * @return bool
198
     */
199
    public function isValid(): bool
200
    {
201
        return $this->valid;
202
    }
203
204
    /**
205
     * Set the Label for a given Field
206
     * @param string $field
207
     * @param string $label
208
     *
209
     * @return $this
210
     */
211
    public function setLabel(string $field, string $label): self
212
    {
213
        $this->labels[$field] = $label;
214
215
        return $this;
216
    }
217
218
    /**
219
     * Return the label for a given Field
220
     * @param string $field
221
     *
222
     * @return string the label if none is set will use the humanize value
223
     * of field
224
     */
225
    public function getLabel(string $field): string
226
    {
227
        return $this->labels[$field] ?? $this->humanizeFieldName($field);
228
    }
229
230
    /**
231
     * Add a filter for the given field
232
     * @param string $field
233
     * @param callable $filter
234
     *
235
     * @return $this
236
     */
237
    public function addFilter(string $field, callable $filter): self
238
    {
239
        if (!isset($this->filters[$field])) {
240
            $this->filters[$field] = [];
241
        }
242
243
        $this->filters[$field][] = $filter;
244
245
        return $this;
246
    }
247
248
    /**
249
     * Add a list of filter for the given field
250
     * @param string $field
251
     * @param callable[] $filters
252
     *
253
     * @return $this
254
     */
255
    public function addFilters(string $field, array $filters): self
256
    {
257
        foreach ($filters as $filter) {
258
            $this->addFilter($field, $filter);
259
        }
260
261
        return $this;
262
    }
263
264
    /**
265
     * Add a rule for the given field
266
     * @param string $field
267
     * @param RuleInterface $rule
268
     *
269
     * @return $this
270
     */
271
    public function addRule(string $field, RuleInterface $rule): self
272
    {
273
        if (!isset($this->rules[$field])) {
274
            $this->rules[$field] = [];
275
        }
276
277
        if (!isset($this->labels[$field])) {
278
            $this->labels[$field] = $this->humanizeFieldName($field);
279
        }
280
281
        $this->rules[$field][] = $rule;
282
283
        return $this;
284
    }
285
286
    /**
287
     * Add a list of rules for the given field
288
     * @param string $field
289
     * @param RuleInterface[] $rules the array of RuleInterface
290
     *
291
     * @return $this
292
     */
293
    public function addRules(string $field, array $rules): self
294
    {
295
        foreach ($rules as $rule) {
296
            $this->addRule($field, $rule);
297
        }
298
299
        return $this;
300
    }
301
302
    /**
303
     * Return all currently defined rules
304
     * @param string $field if set will return only for this field
305
     *
306
     * @return RuleInterface[]|array<string, RuleInterface[]>
307
     */
308
    public function getRules(?string $field = null): array
309
    {
310
        return $field !== null
311
                    ? (isset($this->rules[$field]) ? $this->rules[$field] : [])
312
                    : $this->rules;
313
    }
314
315
    /**
316
     * Validate the data
317
     * @param  array<string, mixed>  $data
318
     * @return bool the validation status
319
     */
320
    public function validate(array $data = []): bool
321
    {
322
        if (count($data) > 0) {
323
            $this->data = $data;
324
        }
325
326
        $this->applyFilters();
327
328
        $this->errors = $this->validateRules();
329
330
        return $this->valid = count($this->errors) === 0;
331
    }
332
333
    /**
334
     * Return the validation errors
335
     * @return array<string, string> the validation errors
336
     *
337
     * @example array(
338
     *          'field1' => 'message 1',
339
     *          'field2' => 'message 2',
340
     * )
341
     */
342
    public function getErrors(): array
343
    {
344
        return $this->errors;
345
    }
346
347
    /**
348
     * Process to validation of fields rules
349
     * @return array<string, string> the validation errors
350
     */
351
    protected function validateRules(): array
352
    {
353
        if (count($this->rules) === 0) {
354
            return [];
355
        }
356
        $errors = [];
357
358
        foreach ($this->rules as $field => $rules) {
359
            list($result, $error) = $this->validateFieldRules($field, $rules);
360
            if ($result === false) {
361
                $errors[$field] = $error;
362
            }
363
        }
364
365
        return $errors;
366
    }
367
368
    /**
369
     * Validate the rules for the given field
370
     * @param  string $field
371
     * @param  RuleInterface[]  $rules the array of rules
372
     * @return array<mixed>     array(Status, error)
373
     */
374
    protected function validateFieldRules(string $field, array $rules): array
375
    {
376
        $value = $this->data[$field] ?? null;
377
        foreach ($rules as $rule) {
378
            list($result, $error) = $this->validateRule($field, $value, $rule);
379
            if ($result === false) {
380
                return [false, $error];
381
            }
382
        }
383
384
        return [true, null];
385
    }
386
387
    /**
388
     * Validate single rule for the given field
389
     * @param  string $field
390
     * @param  mixed $value
391
     * @param  RuleInterface  $rule the rule instance to validate
392
     * @return array<mixed>  array(Status, error)
393
     */
394
    protected function validateRule(string $field, mixed $value, RuleInterface $rule): array
395
    {
396
        $result = $rule->validate($field, $value, $this);
397
        if ($result) {
398
            return [true, null];
399
        }
400
401
        return [false, $rule->getErrorMessage($field, $value, $this)];
402
    }
403
404
    /**
405
     * Apply any defined filters to the validation data
406
     * @return array<string, mixed> the filtered data
407
     */
408
    protected function applyFilters(): array
409
    {
410
        if (count($this->filters) === 0) {
411
            return $this->data;
412
        }
413
414
        $data = $this->data;
415
        foreach ($this->filters as $field => $filters) {
416
            if (!isset($data[$field])) {
417
                continue;
418
            }
419
420
            $value = $data[$field];
421
            foreach ($filters as $filter) {
422
                $value = call_user_func($filter, $value);
423
            }
424
425
            $data[$field] = $value;
426
        }
427
        $this->data = $data;
428
429
        return $this->data;
430
    }
431
432
    /**
433
     * Humanize the given field
434
     * @param  string $field
435
     *
436
     * @return string
437
     */
438
    protected function humanizeFieldName(string $field): string
439
    {
440
        return str_replace(['-', '_'], ' ', $field);
441
    }
442
}
443