Completed
Push — master ( 9785ad...b39d72 )
by Alexis
01:55
created

Validator::validate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 4
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
     *
89
     * @return self
90
     */
91 View Code Duplication
    public function array(array $array, array $rules, $group = null, array $messages = [])
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...
92
    {
93
        foreach ($rules as $key => $options) {
94
            $value = $array[$key] ?? null;
95
96
            $this->value($value, $options, $key, $group, $messages);
97
        }
98
99
        return $this;
100
    }
101
102
    /**
103
     * Validates an objects properties with the given rules.
104
     *
105
     * @param object   $object
106
     * @param array    $rules
107
     * @param string   $group
108
     * @param string[] $messages
109
     *
110
     * @return self
111
     */
112
    public function object($object, array $rules, $group = null, array $messages = [])
113
    {
114
        if (!is_object($object)) {
115
            throw new InvalidArgumentException('The first argument should be an object');
116
        }
117
118
        foreach ($rules as $property => $options) {
119
            $value = $this->getPropertyValue($object, $property);
120
121
            $this->value($value, $options, $property, $group, $messages);
122
        }
123
124
        return $this;
125
    }
126
127
    /**
128
     * Validates request parameters with the given rules.
129
     *
130
     * @param Request  $request
131
     * @param array    $rules
132
     * @param string   $group
133
     * @param string[] $messages
134
     *
135
     * @return self
136
     */
137 View Code Duplication
    public function request(Request $request, array $rules, $group = null, array $messages = [])
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...
138
    {
139
        foreach ($rules as $param => $options) {
140
            $value = $this->getRequestParam($request, $param);
141
142
            $this->value($value, $options, $param, $group, $messages);
143
        }
144
145
        return $this;
146
    }
147
148
    /**
149
     * Validates request parameters, an array or an objects properties.
150
     *
151
     * @param Request|object|array $input
152
     * @param array                $rules
153
     * @param string               $group
154
     * @param string[]             $messages
155
     *
156
     * @return self
157
     */
158
    public function validate($input, array $rules, $group = null, array $messages = [])
159
    {
160
        if ($input instanceof Request) {
161
            return $this->request($input, $rules, $group, $messages);
162
        } elseif (is_array($input)) {
163
            return $this->array($input, $rules, $group, $messages);
164
        } elseif (is_object($input)) {
165
            return $this->object($input, $rules, $group, $messages);
166
        }
167
168
        return $this->value($input, $rules, null, $group, $messages);
169
    }
170
171
    /**
172
     * Validates a single value with the given rules.
173
     *
174
     * @param mixed       $value
175
     * @param AllOf|array $rules
176
     * @param string      $key
177
     * @param string      $group
178
     * @param string[]    $messages
179
     *
180
     * @return self
181
     */
182
    public function value($value, $rules, $key, $group = null, array $messages = [])
183
    {
184
        $config = new Configuration($rules, $key, $group);
185
186
        try {
187
            $config->getValidationRules()->assert($value);
188
        } catch (NestedValidationException $e) {
189
            $this->handleException($e, $config, $messages);
190
        }
191
192
        $this->setValue($config->getKey(), $value, $config->getGroup());
193
194
        return $this;
195
    }
196
197
    /**
198
     * Gets the error count.
199
     *
200
     * @return int
201
     */
202
    public function count()
203
    {
204
        return count($this->errors);
205
    }
206
207
    /**
208
     * Adds a validation error.
209
     *
210
     * @param string $key
211
     * @param string $message
212
     * @param string $group
213
     *
214
     * @return self
215
     */
216
    public function addError($key, $message, $group = null)
217
    {
218 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...
219
            $this->errors[$group][$key][] = $message;
220
        } else {
221
            $this->errors[$key][] = $message;
222
        }
223
224
        return $this;
225
    }
226
227
    /**
228
     * Gets one default messages.
229
     *
230
     * @param string $key
231
     *
232
     * @return string
233
     */
234
    public function getDefaultMessage($key)
235
    {
236
        return $this->defaultMessages[$key] ?? '';
237
    }
238
239
    /**
240
     * Gets all default messages.
241
     *
242
     * @return string[]
243
     */
244
    public function getDefaultMessages()
245
    {
246
        return $this->defaultMessages;
247
    }
248
249
    /**
250
     * Gets one error.
251
     *
252
     * @param string $key
253
     * @param string $index
254
     * @param string $group
255
     *
256
     * @return string
257
     */
258
    public function getError($key, $index = null, $group = null)
259
    {
260
        if (null === $index) {
261
            return $this->getFirstError($key, $group);
262
        }
263
264
        if (!empty($group)) {
265
            return $this->errors[$group][$key][$index] ?? '';
266
        }
267
268
        return $this->errors[$key][$index] ?? '';
269
    }
270
271
    /**
272
     * Gets multiple errors.
273
     *
274
     * @param string $key
275
     * @param string $group
276
     *
277
     * @return string[]
278
     */
279
    public function getErrors($key = null, $group = null)
280
    {
281
        if (!empty($key)) {
282
            if (!empty($group)) {
283
                return $this->errors[$group][$key] ?? [];
284
            }
285
286
            return $this->errors[$key] ?? [];
287
        }
288
289
        return $this->errors;
290
    }
291
292
    /**
293
     * Gets the first error of a parameter.
294
     *
295
     * @param string $key
296
     * @param string $group
297
     *
298
     * @return string
299
     */
300
    public function getFirstError($key, $group = null)
301
    {
302
        if (!empty($group)) {
303
            if (isset($this->errors[$group][$key])) {
304
                $first = array_slice($this->errors[$group][$key], 0, 1);
305
306
                return array_shift($first);
307
            }
308
        }
309
310
        if (isset($this->errors[$key])) {
311
            $first = array_slice($this->errors[$key], 0, 1);
312
313
            return array_shift($first);
314
        }
315
316
        return '';
317
    }
318
319
    /**
320
     * Gets a value from the validated data.
321
     *
322
     * @param string $key
323
     * @param string $group
324
     *
325
     * @return mixed
326
     */
327
    public function getValue($key, $group = null)
328
    {
329
        if (!empty($group)) {
330
            return $this->values[$group][$key] ?? null;
331
        }
332
333
        return $this->values[$key] ?? null;
334
    }
335
336
    /**
337
     * Gets the validated data.
338
     *
339
     * @param string $group
340
     *
341
     * @return array
342
     */
343
    public function getValues($group = null)
344
    {
345
        if (!empty($group)) {
346
            return $this->values[$group] ?? [];
347
        }
348
349
        return $this->values;
350
    }
351
352
    /**
353
     * Gets the errors storage mode.
354
     *
355
     * @return bool
356
     */
357
    public function getShowValidationRules()
358
    {
359
        return $this->showValidationRules;
360
    }
361
362
    /**
363
     * Removes validation errors.
364
     *
365
     * @param string $key
366
     * @param string $group
367
     *
368
     * @return self
369
     */
370
    public function removeErrors($key = null, $group = null)
371
    {
372
        if (!empty($group)) {
373
            if (!empty($key)) {
374
                if (isset($this->errors[$group][$key])) {
375
                    unset($this->errors[$group][$key]);
376
                }
377
            } elseif (isset($this->errors[$group])) {
378
                unset($this->errors[$group]);
379
            }
380
        } elseif (!empty($key)) {
381
            if (isset($this->errors[$key])) {
382
                unset($this->errors[$key]);
383
            }
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 The parameter key.
527
     * @param  string  $default The default value.
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 handleException(NestedValidationException $e, Configuration $config, array $messages = [])
556
    {
557
        // If the 'message' key exists, set it as only message for this param
558
        if ($config->hasMessage()) {
559
            $this->setErrors([$config->getMessage()], $config->getKey(), $config->getGroup());
560
        } else {
561
            // If the 'messages' key exists, override global messages
562
            $this->storeErrors($e, $config, $messages);
563
        }
564
    }
565
566
    /**
567
     * Merges default messages, global messages and individual messages.
568
     *
569
     * @param array $errors
570
     *
571
     * @return string[]
572
     */
573
    protected function mergeMessages(array $errors)
574
    {
575
        $errors = array_filter(call_user_func_array('array_merge', $errors));
576
577
        return $this->showValidationRules ? $errors : array_values($errors);
578
    }
579
580
    /**
581
     * Sets error messages after validation.
582
     *
583
     * @param NestedValidationException $e
584
     * @param Configuration             $config
585
     * @param string[]                  $messages
586
     */
587
    protected function storeErrors(NestedValidationException $e, Configuration $config, array $messages = [])
588
    {
589
        $rules = $config->getValidationRules()->getRules();
590
591
        // Get the names of all rules used for this param
592
        $rulesNames = [];
593
        foreach ($rules as $rule) {
594
            $rulesNames[] = lcfirst((new ReflectionClass($rule))->getShortName());
595
        }
596
597
        $errors = [
598
            $e->findMessages($rulesNames)
599
        ];
600
601
        // If default messages are defined
602
        if (!empty($this->defaultMessages)) {
603
            $errors[] = $e->findMessages($this->defaultMessages);
604
        }
605
606
        // If global messages are defined
607
        if (!empty($messages)) {
608
            $errors[] = $e->findMessages($messages);
609
        }
610
611
        // If individual messages are defined
612
        if ($config->hasMessages()) {
613
            $errors[] = $e->findMessages($config->getMessages());
614
        }
615
616
        $this->setErrors($this->mergeMessages($errors), $config->getKey(), $config->getGroup());
617
    }
618
}
619