Completed
Push — master ( ee29d0...64ce47 )
by Alexis
05:51
created

Validator::getShowValidationRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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 (!is_object($input) && !is_array($input)) {
161
            throw new InvalidArgumentException('The input must be either an object or an array');
162
        }
163
164
        if ($input instanceof Request) {
165
            return $this->request($input, $rules, $group, $messages);
166
        } elseif (is_array($input)) {
167
            return $this->array($input, $rules, $group, $messages);
168
        } elseif (is_object($input)) {
169
            return $this->object($input, $rules, $group, $messages);
170
        }
171
172
        return $this;
173
    }
174
175
    /**
176
     * Validates a single value with the given rules.
177
     *
178
     * @param mixed       $value
179
     * @param AllOf|array $rules
180
     * @param string      $key
181
     * @param string      $group
182
     * @param string[]    $messages
183
     *
184
     * @return self
185
     */
186
    public function value($value, $rules, $key, $group = null, array $messages = [])
187
    {
188
        try {
189
            if ($rules instanceof AllOf) {
190
                $rules->assert($value);
191
            } else {
192
                if (!is_array($rules) || !isset($rules['rules']) || !($rules['rules'] instanceof AllOf)) {
193
                    throw new InvalidArgumentException('Validation rules are missing');
194
                }
195
196
                $rules['rules']->assert($value);
197
            }
198
        } catch (NestedValidationException $e) {
199
            $this->handleException($e, $rules, $key, $group, $messages);
200
        }
201
202
        $this->setValue($key, $value, $group);
203
204
        return $this;
205
    }
206
207
    /**
208
     * Gets the error count.
209
     *
210
     * @return int
211
     */
212
    public function count()
213
    {
214
        return count($this->errors);
215
    }
216
217
    /**
218
     * Adds a validation error.
219
     *
220
     * @param string $key
221
     * @param string $message
222
     * @param string $group
223
     *
224
     * @return self
225
     */
226
    public function addError($key, $message, $group = null)
227
    {
228 View Code Duplication
        if (null !== $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...
229
            $this->errors[$group][$key][] = $message;
230
        } else {
231
            $this->errors[$key][] = $message;
232
        }
233
234
        return $this;
235
    }
236
237
    /**
238
     * Gets one default messages.
239
     *
240
     * @param string $key
241
     *
242
     * @return string
243
     */
244
    public function getDefaultMessage($key)
245
    {
246
        return $this->defaultMessages[$key] ?? '';
247
    }
248
249
    /**
250
     * Gets all default messages.
251
     *
252
     * @return string[]
253
     */
254
    public function getDefaultMessages()
255
    {
256
        return $this->defaultMessages;
257
    }
258
259
    /**
260
     * Gets one error.
261
     *
262
     * @param string $key
263
     * @param string $index
264
     * @param string $group
265
     *
266
     * @return string
267
     */
268
    public function getError($key, $index = null, $group = null)
269
    {
270
        if (null === $index) {
271
            return $this->getFirstError($key, $group);
272
        }
273
274
        if (null !== $group) {
275
            return $this->errors[$group][$key][$index] ?? '';
276
        }
277
278
        return $this->errors[$key][$index] ?? '';
279
    }
280
281
    /**
282
     * Gets multiple errors.
283
     *
284
     * @param string $key
285
     * @param string $group
286
     *
287
     * @return string[]
288
     */
289
    public function getErrors($key = null, $group = null)
290
    {
291
        if (null !== $key) {
292
            if (null !== $group) {
293
                return $this->errors[$group][$key] ?? [];
294
            }
295
296
            return $this->errors[$key] ?? [];
297
        }
298
299
        return $this->errors;
300
    }
301
302
    /**
303
     * Gets the first error of a parameter.
304
     *
305
     * @param string $key
306
     * @param string $group
307
     *
308
     * @return string
309
     */
310
    public function getFirstError($key, $group = null)
311
    {
312 View Code Duplication
        if (null !== $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...
313
            if (isset($this->errors[$group][$key])) {
314
                $first = array_slice($this->errors[$group][$key], 0, 1);
315
316
                return array_shift($first);
317
            }
318
        }
319
320 View Code Duplication
        if (isset($this->errors[$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...
321
            $first = array_slice($this->errors[$key], 0, 1);
322
323
            return array_shift($first);
324
        }
325
326
        return '';
327
    }
328
329
    /**
330
     * Gets a value from the validated data.
331
     *
332
     * @param string $key
333
     * @param string $group
334
     *
335
     * @return string
336
     */
337
    public function getValue($key, $group = null)
338
    {
339
        if (null !== $group) {
340
            return $this->values[$group][$key] ?? '';
341
        }
342
343
        return $this->values[$key] ?? '';
344
    }
345
346
    /**
347
     * Gets the validated data.
348
     *
349
     * @return array
350
     */
351
    public function getValues()
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 (null !== $group) {
377
            if (null !== $key) {
378 View Code Duplication
                if (isset($this->errors[$group][$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...
379
                    $this->errors[$group][$key] = [];
380
                }
381
            } elseif (isset($this->errors[$group])) {
382
                $this->errors[$group] = [];
383
            }
384 View Code Duplication
        } elseif (null !== $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...
385
            if (isset($this->errors[$key])) {
386
                $this->errors[$key] = [];
387
            }
388
        }
389
390
        return $this;
391
    }
392
393
    /**
394
     * Sets the default error message for a validation rule.
395
     *
396
     * @param string $rule
397
     * @param string $message
398
     *
399
     * @return self
400
     */
401
    public function setDefaultMessage($rule, $message)
402
    {
403
        $this->defaultMessages[$rule] = $message;
404
405
        return $this;
406
    }
407
408
    /**
409
     * Sets default error messages.
410
     *
411
     * @param string[] $messages
412
     *
413
     * @return self
414
     */
415
    public function setDefaultMessages(array $messages)
416
    {
417
        $this->defaultMessages = $messages;
418
419
        return $this;
420
    }
421
422
    /**
423
     * Sets validation errors.
424
     *
425
     * @param string[] $errors
426
     * @param string   $key
427
     * @param string   $group
428
     *
429
     * @return self
430
     */
431
    public function setErrors(array $errors, $key = null, $group = null)
432
    {
433
        if (null !== $group) {
434 View Code Duplication
            if (null !== $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...
435
                $this->errors[$group][$key] = $errors;
436
            } else {
437
                $this->errors[$group] = $errors;
438
            }
439
        } elseif (null !== $key) {
440
            $this->errors[$key] = $errors;
441
        } else {
442
            $this->errors = $errors;
443
        }
444
445
        return $this;
446
    }
447
448
    /**
449
     * Sets the errors storage mode.
450
     *
451
     * @param bool $showValidationRules
452
     *
453
     * @return self
454
     */
455
    public function setShowValidationRules(bool $showValidationRules)
456
    {
457
        $this->showValidationRules = $showValidationRules;
458
459
        return $this;
460
    }
461
462
    /**
463
     * Sets the value of a parameter.
464
     *
465
     * @param string $key
466
     * @param mixed  $value
467
     * @param string $group
468
     *
469
     * @return self
470
     */
471
    public function setValue($key, $value, $group = null)
472
    {
473
        if (null !== $group) {
474
            $this->values[$group][$key] = $value;
475
        } else {
476
            $this->values[$key] = $value;
477
        }
478
479
        return $this;
480
    }
481
482
    /**
483
     * Sets the values of request parameters.
484
     *
485
     * @param array $values
486
     *
487
     * @return self
488
     */
489
    public function setValues(array $values)
490
    {
491
        $this->values = $values;
492
493
        return $this;
494
    }
495
496
    /**
497
     * Gets the value of a property of an object.
498
     *
499
     * @param object $object
500
     * @param string $propertyName
501
     * @param mixed  $default
502
     *
503
     * @return mixed
504
     */
505
    protected function getPropertyValue($object, string $propertyName, $default = null)
506
    {
507
        if (!is_object($object)) {
508
            throw new InvalidArgumentException('The first argument should be an object');
509
        }
510
511
        if (!property_exists($object, $propertyName)) {
512
            return $default;
513
        }
514
515
        $reflectionProperty = new ReflectionProperty($object, $propertyName);
516
        $reflectionProperty->setAccessible(true);
517
518
        return $reflectionProperty->getValue($object);
519
    }
520
521
    /**
522
     * Fetch request parameter value from body or query string (in that order).
523
     *
524
     * @param  Request $request
525
     * @param  string  $key The parameter key.
526
     * @param  string  $default The default value.
527
     *
528
     * @return mixed The parameter value.
529
     */
530
    protected function getRequestParam(Request $request, $key, $default = null)
531
    {
532
        $postParams = $request->getParsedBody();
533
        $getParams = $request->getQueryParams();
534
535
        $result = $default;
536
        if (is_array($postParams) && isset($postParams[$key])) {
537
            $result = $postParams[$key];
538
        } elseif (is_object($postParams) && property_exists($postParams, $key)) {
539
            $result = $postParams->$key;
540
        } elseif (isset($getParams[$key])) {
541
            $result = $getParams[$key];
542
        }
543
544
        return $result;
545
    }
546
547
    /**
548
     * Handles a validation exception.
549
     *
550
     * @param NestedValidationException $e
551
     * @param AllOf|array               $options
552
     * @param string                    $key
553
     * @param string                    $group
554
     * @param string[]                  $messages
555
     */
556
    protected function handleException(NestedValidationException $e, $options, $key, $group = null, array $messages = [])
557
    {
558
        // If the 'message' key exists, set it as only message for this param
559
        if (is_array($options) && isset($options['message'])) {
560
            if (!is_string($options['message'])) {
561
                throw new InvalidArgumentException(sprintf('Expected custom message to be of type string, %s given', gettype($options['message'])));
562
            }
563
564
            $this->setErrors([$options['message']], $key, $group);
565
        } else {
566
            // If the 'messages' key exists, override global messages
567
            $this->storeErrors($e, $options, $key, $group, $messages);
568
        }
569
    }
570
571
    /**
572
     * Sets error messages after validation.
573
     *
574
     * @param NestedValidationException $e
575
     * @param AllOf|array               $options
576
     * @param string                    $key
577
     * @param string                    $group
578
     * @param string[]                  $messages
579
     */
580
    protected function storeErrors(NestedValidationException $e, $options, $key, $group = null, array $messages = [])
581
    {
582
        $rules = $options instanceof AllOf ? $options->getRules() : $options['rules']->getRules();
583
584
        // Get the names of all rules used for this param
585
        $rulesNames = [];
586
        foreach ($rules as $rule) {
587
            $rulesNames[] = lcfirst((new ReflectionClass($rule))->getShortName());
588
        }
589
590
        $errors = [
591
            $e->findMessages($rulesNames)
592
        ];
593
594
        // If default messages are defined
595
        if (!empty($this->defaultMessages)) {
596
            $errors[] = $e->findMessages($this->defaultMessages);
597
        }
598
599
        // If global messages are defined
600
        if (!empty($messages)) {
601
            $errors[] = $e->findMessages($messages);
602
        }
603
604
        // If individual messages are defined
605
        if (is_array($options) && !empty($options['messages'])) {
606
            $errors[] = $e->findMessages($options['messages']);
607
        }
608
609
        $errors = array_filter(call_user_func_array('array_merge', $errors));
610
611
        $this->setErrors($this->showValidationRules ? $errors : array_values($errors), $key, $group);
612
    }
613
}
614