Completed
Push — master ( 6e746a...f6a843 )
by Alexis
03:32
created

Validator::removeErrors()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 2
1
<?php
2
3
/*
4
 * This file is part of the awurth/slim-validation package.
5
 *
6
 * (c) Alexis Wurth <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Awurth\SlimValidation;
13
14
use InvalidArgumentException;
15
use Psr\Http\Message\ServerRequestInterface as Request;
16
use ReflectionClass;
17
use ReflectionProperty;
18
use Respect\Validation\Exceptions\NestedValidationException;
19
use Respect\Validation\Rules\AllOf;
20
21
/**
22
 * Validator.
23
 *
24
 * @author Alexis Wurth <[email protected]>
25
 */
26
class Validator
27
{
28
    /**
29
     * The default error messages for the given rules.
30
     *
31
     * @var string[]
32
     */
33
    protected $defaultMessages;
34
35
    /**
36
     * The list of validation errors.
37
     *
38
     * @var string[]
39
     */
40
    protected $errors;
41
42
    /**
43
     * Tells whether errors should be stored in an associative array
44
     * with validation rules as the key, or in an indexed array.
45
     *
46
     * @var bool
47
     */
48
    protected $showValidationRules;
49
50
    /**
51
     * The validated data.
52
     *
53
     * @var array
54
     */
55
    protected $values;
56
57
    /**
58
     * Constructor.
59
     *
60
     * @param bool     $showValidationRules
61
     * @param string[] $defaultMessages
62
     */
63
    public function __construct(bool $showValidationRules = true, array $defaultMessages = [])
64
    {
65
        $this->showValidationRules = $showValidationRules;
66
        $this->defaultMessages = $defaultMessages;
67
        $this->errors = [];
68
        $this->values = [];
69
    }
70
71
    /**
72
     * Tells if there is no error.
73
     *
74
     * @return bool
75
     */
76
    public function isValid()
77
    {
78
        return empty($this->errors);
79
    }
80
81
    /**
82
     * Validates an array with the given rules.
83
     *
84
     * @param array    $array
85
     * @param array    $rules
86
     * @param string   $group
87
     * @param string[] $messages
88
     * @param mixed    $default
89
     *
90
     * @return self
91
     */
92
    public function array(array $array, array $rules, $group = null, array $messages = [], $default = null)
93
    {
94
        foreach ($rules as $key => $options) {
95
            $config = new Configuration($options, $key, $group, $default);
96
97
            $value = $array[$key] ?? $config->getDefault();
98
99
            $this->validateInput($value, $config, $messages);
100
        }
101
102
        return $this;
103
    }
104
105
    /**
106
     * Validates an objects properties with the given rules.
107
     *
108
     * @param object   $object
109
     * @param array    $rules
110
     * @param string   $group
111
     * @param string[] $messages
112
     * @param mixed    $default
113
     *
114
     * @return self
115
     */
116 View Code Duplication
    public function object($object, array $rules, $group = null, array $messages = [], $default = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
117
    {
118
        if (!is_object($object)) {
119
            throw new InvalidArgumentException('The first argument should be an object');
120
        }
121
122
        foreach ($rules as $property => $options) {
123
            $config = new Configuration($options, $property, $group, $default);
124
125
            $value = $this->getPropertyValue($object, $property, $config->getDefault());
126
127
            $this->validateInput($value, $config, $messages);
128
        }
129
130
        return $this;
131
    }
132
133
    /**
134
     * Validates request parameters with the given rules.
135
     *
136
     * @param Request  $request
137
     * @param array    $rules
138
     * @param string   $group
139
     * @param string[] $messages
140
     * @param mixed    $default
141
     *
142
     * @return self
143
     */
144 View Code Duplication
    public function request(Request $request, array $rules, $group = null, array $messages = [], $default = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
    {
146
        foreach ($rules as $param => $options) {
147
            $config = new Configuration($options, $param, $group, $default);
148
149
            $value = $this->getRequestParam($request, $param, $config->getDefault());
150
151
            $this->validateInput($value, $config, $messages);
152
        }
153
154
        return $this;
155
    }
156
157
    /**
158
     * Validates request parameters, an array or an objects properties.
159
     *
160
     * @param Request|object|array $input
161
     * @param array                $rules
162
     * @param string               $group
163
     * @param string[]             $messages
164
     * @param mixed                $default
165
     *
166
     * @return self
167
     */
168
    public function validate($input, array $rules, $group = null, array $messages = [], $default = null)
169
    {
170
        if ($input instanceof Request) {
171
            return $this->request($input, $rules, $group, $messages, $default);
172
        } elseif (is_array($input)) {
173
            return $this->array($input, $rules, $group, $messages, $default);
174
        } elseif (is_object($input)) {
175
            return $this->object($input, $rules, $group, $messages, $default);
176
        }
177
178
        return $this->value($input, $rules, null, $group, $messages);
179
    }
180
181
    /**
182
     * Validates a single value with the given rules.
183
     *
184
     * @param mixed       $value
185
     * @param AllOf|array $rules
186
     * @param string      $key
187
     * @param string      $group
188
     * @param string[]    $messages
189
     *
190
     * @return self
191
     */
192
    public function value($value, $rules, $key, $group = null, array $messages = [])
193
    {
194
        $config = new Configuration($rules, $key, $group);
195
196
        $this->validateInput($value, $config, $messages);
197
198
        return $this;
199
    }
200
201
    /**
202
     * Gets the error count.
203
     *
204
     * @return int
205
     */
206
    public function count()
207
    {
208
        return count($this->errors);
209
    }
210
211
    /**
212
     * Adds a validation error.
213
     *
214
     * @param string $key
215
     * @param string $message
216
     * @param string $group
217
     *
218
     * @return self
219
     */
220
    public function addError($key, $message, $group = null)
221
    {
222 View Code Duplication
        if (!empty($group)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
            $this->errors[$group][$key][] = $message;
224
        } else {
225
            $this->errors[$key][] = $message;
226
        }
227
228
        return $this;
229
    }
230
231
    /**
232
     * Gets one default messages.
233
     *
234
     * @param string $key
235
     *
236
     * @return string
237
     */
238
    public function getDefaultMessage($key)
239
    {
240
        return $this->defaultMessages[$key] ?? '';
241
    }
242
243
    /**
244
     * Gets all default messages.
245
     *
246
     * @return string[]
247
     */
248
    public function getDefaultMessages()
249
    {
250
        return $this->defaultMessages;
251
    }
252
253
    /**
254
     * Gets one error.
255
     *
256
     * @param string $key
257
     * @param string $index
258
     * @param string $group
259
     *
260
     * @return string
261
     */
262
    public function getError($key, $index = null, $group = null)
263
    {
264
        if (null === $index) {
265
            return $this->getFirstError($key, $group);
266
        }
267
268
        if (!empty($group)) {
269
            return $this->errors[$group][$key][$index] ?? '';
270
        }
271
272
        return $this->errors[$key][$index] ?? '';
273
    }
274
275
    /**
276
     * Gets multiple errors.
277
     *
278
     * @param string $key
279
     * @param string $group
280
     *
281
     * @return string[]
282
     */
283
    public function getErrors($key = null, $group = null)
284
    {
285
        if (!empty($key)) {
286
            if (!empty($group)) {
287
                return $this->errors[$group][$key] ?? [];
288
            }
289
290
            return $this->errors[$key] ?? [];
291
        }
292
293
        return $this->errors;
294
    }
295
296
    /**
297
     * Gets the first error of a parameter.
298
     *
299
     * @param string $key
300
     * @param string $group
301
     *
302
     * @return string
303
     */
304
    public function getFirstError($key, $group = null)
305
    {
306
        if (!empty($group)) {
307
            if (isset($this->errors[$group][$key])) {
308
                $first = array_slice($this->errors[$group][$key], 0, 1);
309
310
                return array_shift($first);
311
            }
312
        }
313
314
        if (isset($this->errors[$key])) {
315
            $first = array_slice($this->errors[$key], 0, 1);
316
317
            return array_shift($first);
318
        }
319
320
        return '';
321
    }
322
323
    /**
324
     * Gets a value from the validated data.
325
     *
326
     * @param string $key
327
     * @param string $group
328
     *
329
     * @return mixed
330
     */
331
    public function getValue($key, $group = null)
332
    {
333
        if (!empty($group)) {
334
            return $this->values[$group][$key] ?? null;
335
        }
336
337
        return $this->values[$key] ?? null;
338
    }
339
340
    /**
341
     * Gets the validated data.
342
     *
343
     * @param string $group
344
     *
345
     * @return array
346
     */
347
    public function getValues($group = null)
348
    {
349
        if (!empty($group)) {
350
            return $this->values[$group] ?? [];
351
        }
352
353
        return $this->values;
354
    }
355
356
    /**
357
     * Gets the errors storage mode.
358
     *
359
     * @return bool
360
     */
361
    public function getShowValidationRules()
362
    {
363
        return $this->showValidationRules;
364
    }
365
366
    /**
367
     * Removes validation errors.
368
     *
369
     * @param string $key
370
     * @param string $group
371
     *
372
     * @return self
373
     */
374
    public function removeErrors($key = null, $group = null)
375
    {
376
        if (!empty($group)) {
377
            if (!empty($key)) {
378
                unset($this->errors[$group][$key]);
379
            } else {
380
                unset($this->errors[$group]);
381
            }
382
        } elseif (!empty($key)) {
383
            unset($this->errors[$key]);
384
        }
385
386
        return $this;
387
    }
388
389
    /**
390
     * Sets the default error message for a validation rule.
391
     *
392
     * @param string $rule
393
     * @param string $message
394
     *
395
     * @return self
396
     */
397
    public function setDefaultMessage($rule, $message)
398
    {
399
        $this->defaultMessages[$rule] = $message;
400
401
        return $this;
402
    }
403
404
    /**
405
     * Sets default error messages.
406
     *
407
     * @param string[] $messages
408
     *
409
     * @return self
410
     */
411
    public function setDefaultMessages(array $messages)
412
    {
413
        $this->defaultMessages = $messages;
414
415
        return $this;
416
    }
417
418
    /**
419
     * Sets validation errors.
420
     *
421
     * @param string[] $errors
422
     * @param string   $key
423
     * @param string   $group
424
     *
425
     * @return self
426
     */
427
    public function setErrors(array $errors, $key = null, $group = null)
428
    {
429
        if (!empty($group)) {
430 View Code Duplication
            if (!empty($key)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
431
                $this->errors[$group][$key] = $errors;
432
            } else {
433
                $this->errors[$group] = $errors;
434
            }
435
        } elseif (!empty($key)) {
436
            $this->errors[$key] = $errors;
437
        } else {
438
            $this->errors = $errors;
439
        }
440
441
        return $this;
442
    }
443
444
    /**
445
     * Sets the errors storage mode.
446
     *
447
     * @param bool $showValidationRules
448
     *
449
     * @return self
450
     */
451
    public function setShowValidationRules(bool $showValidationRules)
452
    {
453
        $this->showValidationRules = $showValidationRules;
454
455
        return $this;
456
    }
457
458
    /**
459
     * Sets the value of a parameter.
460
     *
461
     * @param string $key
462
     * @param mixed  $value
463
     * @param string $group
464
     *
465
     * @return self
466
     */
467 View Code Duplication
    public function setValue($key, $value, $group = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
    {
469
        if (!empty($group)) {
470
            $this->values[$group][$key] = $value;
471
        } else {
472
            $this->values[$key] = $value;
473
        }
474
475
        return $this;
476
    }
477
478
    /**
479
     * Sets values of validated data.
480
     *
481
     * @param array  $values
482
     * @param string $group
483
     *
484
     * @return self
485
     */
486 View Code Duplication
    public function setValues(array $values, $group = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
487
    {
488
        if (!empty($group)) {
489
            $this->values[$group] = $values;
490
        } else {
491
            $this->values = $values;
492
        }
493
494
        return $this;
495
    }
496
497
    /**
498
     * Gets the value of a property of an object.
499
     *
500
     * @param object $object
501
     * @param string $propertyName
502
     * @param mixed  $default
503
     *
504
     * @return mixed
505
     */
506
    protected function getPropertyValue($object, string $propertyName, $default = null)
507
    {
508
        if (!is_object($object)) {
509
            throw new InvalidArgumentException('The first argument should be an object');
510
        }
511
512
        if (!property_exists($object, $propertyName)) {
513
            return $default;
514
        }
515
516
        $reflectionProperty = new ReflectionProperty($object, $propertyName);
517
        $reflectionProperty->setAccessible(true);
518
519
        return $reflectionProperty->getValue($object);
520
    }
521
522
    /**
523
     * Fetch request parameter value from body or query string (in that order).
524
     *
525
     * @param  Request $request
526
     * @param  string  $key
527
     * @param  string  $default
528
     *
529
     * @return mixed The parameter value.
530
     */
531
    protected function getRequestParam(Request $request, $key, $default = null)
532
    {
533
        $postParams = $request->getParsedBody();
534
        $getParams = $request->getQueryParams();
535
536
        $result = $default;
537
        if (is_array($postParams) && isset($postParams[$key])) {
538
            $result = $postParams[$key];
539
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
540
            $result = $postParams->$key;
541
        } elseif (isset($getParams[$key])) {
542
            $result = $getParams[$key];
543
        }
544
545
        return $result;
546
    }
547
548
    /**
549
     * Handles a validation exception.
550
     *
551
     * @param NestedValidationException $e
552
     * @param Configuration             $config
553
     * @param string[]                  $messages
554
     */
555
    protected function handleValidationException(NestedValidationException $e, Configuration $config, array $messages = [])
556
    {
557
        if ($config->hasMessage()) {
558
            $this->setErrors([$config->getMessage()], $config->getKey(), $config->getGroup());
559
        } else {
560
            $this->storeErrors($e, $config, $messages);
561
        }
562
    }
563
564
    /**
565
     * Merges default messages, global messages and individual messages.
566
     *
567
     * @param array $errors
568
     *
569
     * @return string[]
570
     */
571
    protected function mergeMessages(array $errors)
572
    {
573
        $errors = array_filter(call_user_func_array('array_merge', $errors));
574
575
        return $this->showValidationRules ? $errors : array_values($errors);
576
    }
577
578
    /**
579
     * Sets error messages after validation.
580
     *
581
     * @param NestedValidationException $e
582
     * @param Configuration             $config
583
     * @param string[]                  $messages
584
     */
585
    protected function storeErrors(NestedValidationException $e, Configuration $config, array $messages = [])
586
    {
587
        $rules = $config->getValidationRules()->getRules();
588
589
        // Get the names of all rules used for this param
590
        $rulesNames = [];
591
        foreach ($rules as $rule) {
592
            $rulesNames[] = lcfirst((new ReflectionClass($rule))->getShortName());
593
        }
594
595
        $errors = [
596
            $e->findMessages($rulesNames)
597
        ];
598
599
        // If default messages are defined
600
        if (!empty($this->defaultMessages)) {
601
            $errors[] = $e->findMessages($this->defaultMessages);
602
        }
603
604
        // If global messages are defined
605
        if (!empty($messages)) {
606
            $errors[] = $e->findMessages($messages);
607
        }
608
609
        // If individual messages are defined
610
        if ($config->hasMessages()) {
611
            $errors[] = $e->findMessages($config->getMessages());
612
        }
613
614
        $this->setErrors($this->mergeMessages($errors), $config->getKey(), $config->getGroup());
615
    }
616
617
    /**
618
     * Executes the validation of a value and handles errors.
619
     *
620
     * @param mixed         $input
621
     * @param Configuration $config
622
     * @param string[]      $messages
623
     */
624
    protected function validateInput($input, Configuration $config, array $messages = [])
625
    {
626
        try {
627
            $config->getValidationRules()->assert($input);
628
        } catch (NestedValidationException $e) {
629
            $this->handleValidationException($e, $config, $messages);
630
        }
631
632
        $this->setValue($config->getKey(), $input, $config->getGroup());
633
    }
634
}
635