Validator::addError()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 3.0.0
13
 */
14
15
namespace Quantum\Libraries\Validation;
16
17
use Quantum\Libraries\Lang\Exceptions\LangException;
18
use Quantum\Libraries\Validation\Traits\Resource;
19
use Quantum\Libraries\Validation\Traits\General;
20
use Quantum\Libraries\Validation\Traits\Length;
21
use Quantum\Libraries\Validation\Traits\Lists;
22
use Quantum\Config\Exceptions\ConfigException;
23
use Quantum\Libraries\Validation\Traits\Type;
24
use Quantum\Libraries\Validation\Traits\File;
25
use Quantum\Di\Exceptions\DiException;
26
use BadMethodCallException;
27
use ReflectionException;
28
use RuntimeException;
29
use Closure;
30
31
/**
32
 * Class Validator
33
 * @package Quantum\Libraries\Validation
34
 */
35
class Validator
36
{
37
    use General;
38
    use Type;
39
    use File;
40
    use Lists;
41
    use Length;
42
    use Resource;
43
44
    /**
45
     * Rules
46
     * @var array
47
     */
48
    private $rules = [];
49
50
    /**
51
     * Validation Errors
52
     * @var array
53
     */
54
    private $errors = [];
55
56
    /**
57
     * Request data
58
     * @var array
59
     */
60
    private $data = [];
61
62
    /**
63
     * Custom validation callbacks
64
     * @var Closure[]
65
     */
66
    private $customRules = [];
67
68
    /**
69
     * Add rules for a single field
70
     * @param string $field
71
     * @param array $rules Format: [['ruleName' => param], ...]
72
     */
73
    public function setRule(string $field, array $rules)
74
    {
75
        foreach ($rules as $rule) {
76
            $ruleName = key($rule);
77
            $ruleParam = current($rule);
78
            $this->setOrUpdateRule($field, $ruleName, $ruleParam);
79
        }
80
    }
81
82
    /**
83
     * Add multiple rules for multiple fields
84
     * @param array $rules Format: ['field' => [ ['rule' => param], ... ], ...]
85
     */
86
    public function setRules(array $rules): void
87
    {
88
        foreach ($rules as $field => $fieldRules) {
89
            $this->setRule($field, $fieldRules);
90
        }
91
    }
92
93
    /**
94
     * Update a single rule for a field if exists
95
     * @param string $field
96
     * @param array $rule Format: ['ruleName' => param]
97
     */
98
    public function updateRule(string $field, array $rule)
99
    {
100
        $ruleName = key($rule);
101
        $ruleParam = current($rule);
102
        $this->setOrUpdateRule($field, $ruleName, $ruleParam);
103
    }
104
105
    /**
106
     * Delete a rule or all rules for a given field
107
     * @param string $field
108
     * @param string|null $rule Specific rule to delete; if null deletes all rules for field
109
     * @return void
110
     */
111
    public function deleteRule(string $field, ?string $rule = null): void
112
    {
113
        if (!isset($this->rules[$field])) {
114
            return;
115
        }
116
117
        if ($rule !== null) {
118
            unset($this->rules[$field][$rule]);
119
120
            if (empty($this->rules[$field])) {
121
                unset($this->rules[$field]);
122
            }
123
        } else {
124
            unset($this->rules[$field]);
125
        }
126
    }
127
128
    /**
129
     * Flush all rules and errors
130
     * @return void
131
     */
132
    public function flushRules(): void
133
    {
134
        $this->rules = [];
135
        $this->flushErrors();
136
    }
137
138
    /**
139
     * Validate given data against defined rules
140
     * @param array $data
141
     * @return bool True if valid, false otherwise
142
     */
143
    public function isValid(array $data): bool
144
    {
145
        $this->data = $data;
146
        $this->flushErrors();
147
148
        if ($this->rules === []) {
149
            return true;
150
        }
151
152
        foreach ($this->rules as $field => $_) {
153
            if (!array_key_exists($field, $data)) {
154
                $data[$field] = '';
155
            }
156
        }
157
158
        foreach ($data as $field => $value) {
159
            if (!isset($this->rules[$field])) {
160
                continue;
161
            }
162
163
            foreach ($this->rules[$field] as $rule => $params) {
164
                $ruleParams = is_array($params) ? $params : [$params];
165
166
                if (method_exists($this, $rule)) {
167
                    $isValid = $this->$rule($value, ...$ruleParams);
168
                } elseif (isset($this->customRules[$rule])) {
169
                    $isValid = $this->executeCustomRule($rule, $value, ...$ruleParams);
170
                } else {
171
                    throw new BadMethodCallException("Validation rule '{$rule}' not found.");
172
                }
173
174
                if (!$isValid) {
175
                    $this->addError($field, $rule, ...$ruleParams);
0 ignored issues
show
Bug introduced by
$ruleParams is expanded, but the parameter $param of Quantum\Libraries\Validation\Validator::addError() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
                    $this->addError($field, $rule, /** @scrutinizer ignore-type */ ...$ruleParams);
Loading history...
176
                }
177
            }
178
        }
179
180
        return $this->errors === [];
181
    }
182
183
    /**
184
     * Add a custom validation rule
185
     * @param string $rule Rule name
186
     * @param Closure $function Callback function with signature function($value, $param): bool
187
     * @return void
188
     */
189
    public function addRule(string $rule, Closure $function): void
190
    {
191
        if ($rule === '' || $rule === '0') {
192
            return;
193
        }
194
195
        $this->customRules[$rule] = $function;
196
    }
197
198
    /**
199
     * Gets validation errors with translations
200
     * @return array
201
     * @throws ConfigException
202
     * @throws DiException
203
     * @throws LangException
204
     * @throws ReflectionException
205
     */
206
    public function getErrors(): array
207
    {
208
        if ($this->errors === []) {
209
            return [];
210
        }
211
212
        $messages = [];
213
214
        foreach ($this->errors as $field => $fieldErrors) {
215
            foreach ($fieldErrors as $rule => $param) {
216
                $translationParams = [t('common.' . $field)];
217
                if ($param !== null && $param !== '') {
218
                    $translationParams[] = $param;
219
                }
220
221
                $messages[$field][] = t("validation.$rule", $translationParams);
222
            }
223
        }
224
225
        return $messages;
226
    }
227
228
    /**
229
     * Adds an error for a field and rule
230
     * @param string $field
231
     * @param string $rule
232
     * @param mixed|null $param
233
     * @return void
234
     */
235
    protected function addError(string $field, string $rule, $param = null): void
236
    {
237
        if (!isset($this->errors[$field])) {
238
            $this->errors[$field] = [];
239
        }
240
241
        $this->errors[$field][$rule] = $param;
242
    }
243
244
    /**
245
     * Flush all errors
246
     * @return void
247
     */
248
    public function flushErrors(): void
249
    {
250
        $this->errors = [];
251
    }
252
253
    /**
254
     * Executes user defined rule
255
     * @param string $rule
256
     * @param $value
257
     * @param mixed ...$params
258
     * @return bool
259
     */
260
    protected function executeCustomRule(string $rule, $value, ...$params): bool
261
    {
262
        $function = $this->customRules[$rule];
263
264
        if (!is_callable($function)) {
265
            throw new RuntimeException("Validation rule '{$rule}' is not callable.");
266
        }
267
268
        return (bool) $function($value, ...$params);
269
    }
270
271
    /**
272
     * @param string $field
273
     * @param string $ruleName
274
     * @param $ruleParam
275
     */
276
    private function setOrUpdateRule(string $field, string $ruleName, $ruleParam): void
277
    {
278
        if (!isset($this->rules[$field])) {
279
            $this->rules[$field] = [];
280
        }
281
282
        $this->rules[$field][$ruleName] = $ruleParam;
283
    }
284
}
285