Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Validator
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
7 / 7
17
100.00% covered (success)
100.00%
1 / 1
 validate
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
11
 map
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mapBack
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addErrors
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 configureValidationFactory
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Http\Validation;
6
7use Illuminate\Support\Str;
8use InvalidArgumentException;
9use ReflectionProperty;
10use Respect\Validation\Exceptions\ComponentException;
11use Respect\Validation\Factory;
12use Respect\Validation\Validator as RespectValidator;
13
14class Validator
15{
16    /** @var string[] */
17    protected array $errors = [];
18
19    protected array $data = [];
20
21    protected array $mapping = [
22        'accepted' => 'Checked',
23        'int'      => 'IntVal',
24        'float'    => 'FloatVal',
25        'required' => 'NotEmpty',
26        'optional' => 'nullable',
27    ];
28
29    public function validate(array $data, array $rules): bool
30    {
31        $this->errors = [];
32        $this->data = [];
33
34        $this->configureValidationFactory();
35
36        $validData = [];
37        foreach ($rules as $fieldName => $rulesList) {
38            $value = $data[$fieldName] ?? null;
39            $rulesList = is_array($rulesList) ? $rulesList : explode('|', $rulesList);
40
41            // Configure the check to be run for every rule
42            foreach ($rulesList as $parameters) {
43                $v = new RespectValidator();
44
45                $parameters = is_array($parameters) ? $parameters : explode(':', $parameters);
46                $rule = array_shift($parameters);
47                $rule = Str::camel($rule);
48                $rule = $this->map($rule);
49
50                // Handle empty/optional values
51                if ($rule == 'nullable') {
52                    if (is_null($value) || $value === '') {
53                        $validData[$fieldName] = null;
54                        break;
55                    }
56
57                    $validData[$fieldName] = $value;
58                    continue;
59                }
60
61                // Configure the validation
62                try {
63                    $v = call_user_func_array([$v, $rule], $parameters);
64                } catch (ComponentException $e) {
65                    throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
66                }
67
68                // Run validation
69                if ($v->validate($value)) {
70                    $validData[$fieldName] = $value;
71                } else {
72                    $this->errors[$fieldName][] = implode('.', ['validation', $fieldName, $this->mapBack($rule)]);
73                }
74            }
75        }
76
77        $success = empty($this->errors);
78        if ($success) {
79            $this->data = $validData;
80        }
81
82        return $success;
83    }
84
85    protected function map(string $rule): string
86    {
87        return $this->mapping[$rule] ?? $rule;
88    }
89
90    protected function mapBack(string $rule): string
91    {
92        $mapping = array_flip($this->mapping);
93
94        return $mapping[$rule] ?? $rule;
95    }
96
97    public function getData(): array
98    {
99        return $this->data;
100    }
101
102    /**
103     * @return string[]
104     */
105    public function getErrors(): array
106    {
107        return $this->errors;
108    }
109
110    public function addErrors(array $errors): self
111    {
112        $this->errors = array_merge($this->errors, $errors);
113
114        return $this;
115    }
116
117    protected function configureValidationFactory(): void
118    {
119        $f = (new Factory())
120            ->withRuleNamespace('\\Engelsystem\\Http\\Validation\\Rules');
121
122        // Hacking around, alternative is to reimplement it...
123        $property = new ReflectionProperty($f, 'rulesNamespaces');
124        $property->setValue($f, array_reverse($property->getValue($f)));
125
126        Factory::setDefaultInstance($f);
127    }
128}