Completed
Push — master ( a12164...2a5888 )
by Arman
16s queued 12s
created

Validator::getErrors()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 5
nop 0
dl 0
loc 20
rs 9.2222
c 0
b 0
f 0
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 2.9.8
13
 */
14
15
namespace Quantum\Libraries\Validation;
16
17
use Quantum\Libraries\Validation\Traits\Resource;
18
use Quantum\Libraries\Validation\Traits\General;
19
use Quantum\Libraries\Validation\Traits\Length;
20
use Quantum\Libraries\Validation\Traits\Lists;
21
use Quantum\Libraries\Validation\Traits\Type;
22
use Quantum\Libraries\Validation\Traits\File;
23
use BadMethodCallException;
24
use RuntimeException;
25
use Closure;
26
27
/**
28
 * Class Validator
29
 * @package Quantum\Libraries\Validation
30
 */
31
class Validator
32
{
33
34
    use General;
35
    use Type;
36
    use File;
37
    use Lists;
38
    use Length;
39
    use Resource;
40
41
    /**
42
     * Rules
43
     * @var array
44
     */
45
    private $rules = [];
46
47
    /**
48
     * Validation Errors
49
     * @var array
50
     */
51
    private $errors = [];
52
53
    /**
54
     * Request data
55
     * @var array
56
     */
57
    private $data = [];
58
59
    /**
60
     * Custom validation callbacks
61
     * @var Closure[]
62
     */
63
    private $customRules = [];
64
65
    /**
66
     * Add rules for a single field
67
     * @param string $field
68
     * @param array $rules Format: [['ruleName' => param], ...]
69
     */
70
    public function setRule(string $field, array $rules)
71
    {
72
        foreach ($rules as $rule) {
73
            $ruleName = key($rule);
74
            $ruleParam = current($rule);
75
            $this->setOrUpdateRule($field, $ruleName, $ruleParam);
76
        }
77
    }
78
79
    /**
80
     * Add multiple rules for multiple fields
81
     * @param array $rules Format: ['field' => [ ['rule' => param], ... ], ...]
82
     */
83
    public function setRules(array $rules): void
84
    {
85
        foreach ($rules as $field => $fieldRules) {
86
            $this->setRule($field, $fieldRules);
87
        }
88
    }
89
90
    /**
91
     * Update a single rule for a field if exists
92
     * @param string $field
93
     * @param array $rule Format: ['ruleName' => param]
94
     */
95
    public function updateRule(string $field, array $rule)
96
    {
97
        $ruleName = key($rule);
98
        $ruleParam = current($rule);
99
        $this->setOrUpdateRule($field, $ruleName, $ruleParam);
100
    }
101
102
    /**
103
     * Delete a rule or all rules for a given field
104
     * @param string $field
105
     * @param string|null $rule Specific rule to delete; if null deletes all rules for field
106
     * @return void
107
     */
108
    public function deleteRule(string $field, string $rule = null): void
109
    {
110
        if (!isset($this->rules[$field])) {
111
            return;
112
        }
113
114
        if ($rule !== null) {
115
            unset($this->rules[$field][$rule]);
116
117
            if (empty($this->rules[$field])) {
118
                unset($this->rules[$field]);
119
            }
120
        } else {
121
            unset($this->rules[$field]);
122
        }
123
    }
124
125
    /**
126
     * Flush all rules and errors
127
     * @return void
128
     */
129
    public function flushRules(): void
130
    {
131
        $this->rules = [];
132
        $this->flushErrors();
133
    }
134
135
    /**
136
     * Validate given data against defined rules
137
     * @param array $data
138
     * @return bool True if valid, false otherwise
139
     */
140
    public function isValid(array $data): bool
141
    {
142
        $this->data = $data;
143
        $this->flushErrors();
144
145
        if (empty($this->rules)) {
146
            return true;
147
        }
148
149
        foreach ($this->rules as $field => $_) {
150
            if (!array_key_exists($field, $data)) {
151
                $data[$field] = '';
152
            }
153
        }
154
155
        foreach ($data as $field => $value) {
156
            if (!isset($this->rules[$field])) {
157
                continue;
158
            }
159
160
            foreach ($this->rules[$field] as $rule => $params) {
161
                $ruleParams = is_array($params) ? $params : [$params];
162
163
                if (method_exists($this, $rule)) {
164
                    $isValid = $this->$rule($field, $value, ...$ruleParams);
165
                } elseif (isset($this->customRules[$rule])) {
166
                    $isValid = $this->executeCustomRule($rule, $field, $value, ...$ruleParams);
167
                } else {
168
                    throw new BadMethodCallException("Validation rule '{$rule}' not found.");
169
                }
170
171
                if (!$isValid) {
172
                    $this->addError($field, $rule, $params);
173
                }
174
            }
175
        }
176
177
        return empty($this->errors);
178
    }
179
180
    /**
181
     * Add a custom validation rule
182
     * @param string $rule Rule name
183
     * @param Closure $function Callback function with signature function($value, $param): bool
184
     * @return void
185
     */
186
    public function addRule(string $rule, Closure $function): void
187
    {
188
        if (empty($rule) || !is_callable($function)) {
189
            return;
190
        }
191
192
        $this->customRules[$rule] = $function;
193
    }
194
195
    /**
196
     * Gets validation errors with translations
197
     * @return array
198
     */
199
    public function getErrors(): array
200
    {
201
        if (empty($this->errors)) {
202
            return [];
203
        }
204
205
        $messages = [];
206
207
        foreach ($this->errors as $field => $fieldErrors) {
208
            foreach ($fieldErrors as $rule => $param) {
209
                $translationParams = [t('common.' . $field)];
210
                if ($param !== null && $param !== '') {
211
                    $translationParams[] = $param;
212
                }
213
214
                $messages[$field][] = t("validation.$rule", $translationParams);
215
            }
216
        }
217
218
        return $messages;
219
    }
220
221
    /**
222
     * Adds an error for a field and rule
223
     * @param string $field
224
     * @param string $rule
225
     * @param mixed|null $param
226
     * @return void
227
     */
228
    protected function addError(string $field, string $rule, $param = null): void
229
    {
230
        if (!isset($this->errors[$field])) {
231
            $this->errors[$field] = [];
232
        }
233
234
        $this->errors[$field][$rule] = $param;
235
    }
236
237
    /**
238
     * Flush all errors
239
     * @return void
240
     */
241
    public function flushErrors(): void
242
    {
243
        $this->errors = [];
244
    }
245
246
    /**
247
     * Executes user defined rule
248
     * @param string $rule
249
     * @param string $field
250
     * @param $value
251
     * @param mixed ...$params
252
     * @return bool
253
     */
254
    protected function executeCustomRule(string $rule, string $field, $value, ...$params): bool
255
    {
256
        $function = $this->customRules[$rule];
257
258
        if (!is_callable($function)) {
259
            throw new RuntimeException("Validation rule '{$rule}' is not callable.");
260
        }
261
262
        return (bool) $function($value, ...$params);
263
    }
264
265
    /**
266
     * @param string $field
267
     * @param string $ruleName
268
     * @param $ruleParam
269
     */
270
    private function setOrUpdateRule(string $field, string $ruleName, $ruleParam): void
271
    {
272
        if (!isset($this->rules[$field])) {
273
            $this->rules[$field] = [];
274
        }
275
276
        $this->rules[$field][$ruleName] = $ruleParam;
277
    }
278
}
279