Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.28% |
57 / 58 |
|
95.00% |
19 / 20 |
CRAP | |
0.00% |
0 / 1 |
ArrayValidator | |
98.28% |
57 / 58 |
|
95.00% |
19 / 20 |
38 | |
0.00% |
0 / 1 |
assertString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertInt | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertFloat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertBool | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertObject | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
assertExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
allowExtraKeys | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
validate | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
isValid | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getMissedKeys | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTypeMismatchKeys | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExtraKeys | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getErrorMessage | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
cleanResults | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateKey | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
validateKeyExistence | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
validateIfKeyTypeCanBeChecked | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
assertType | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
isType | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2019 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | namespace oat\tao\helpers; |
22 | |
23 | class ArrayValidator |
24 | { |
25 | public const STR = 'string'; |
26 | public const INT = 'integer'; |
27 | public const FLOAT = 'float'; |
28 | public const BOOL = 'boolean'; |
29 | public const ARR = 'array'; |
30 | public const OBJ = 'object'; |
31 | |
32 | private static $typeCheckFunctions = [ |
33 | self::STR => 'is_string', |
34 | self::INT => 'is_int', |
35 | self::FLOAT => 'is_float', |
36 | self::BOOL => 'is_bool', |
37 | self::ARR => 'is_array', |
38 | self::OBJ => 'is_object' |
39 | ]; |
40 | |
41 | /** |
42 | * @var array[] |
43 | */ |
44 | private $rules = []; |
45 | |
46 | /** |
47 | * @var bool |
48 | */ |
49 | private $allowExtraKeys = true; |
50 | |
51 | /** |
52 | * @var string[] |
53 | */ |
54 | private $missedKeys; |
55 | |
56 | /** |
57 | * @var string[] |
58 | */ |
59 | private $typeMismatchKeys; |
60 | |
61 | /** |
62 | * @var string[] |
63 | */ |
64 | private $extraKeys; |
65 | |
66 | /** |
67 | * @param int|int[]|string|string[] $key |
68 | * @param bool $required |
69 | * @param bool $nullable |
70 | * @return $this |
71 | */ |
72 | public function assertString($key, $required = true, $nullable = false) |
73 | { |
74 | return $this->assertType($key, self::STR, $required, $nullable); |
75 | } |
76 | |
77 | /** |
78 | * @param int|int[]|string|string[] $key |
79 | * @param bool $required |
80 | * @param bool $nullable |
81 | * @return $this |
82 | */ |
83 | public function assertInt($key, $required = true, $nullable = false) |
84 | { |
85 | return $this->assertType($key, self::INT, $required, $nullable); |
86 | } |
87 | |
88 | /** |
89 | * @param int|int[]|string|string[] $key |
90 | * @param bool $required |
91 | * @param bool $nullable |
92 | * @return $this |
93 | */ |
94 | public function assertFloat($key, $required = true, $nullable = false) |
95 | { |
96 | return $this->assertType($key, self::FLOAT, $required, $nullable); |
97 | } |
98 | |
99 | /** |
100 | * @param int|int[]|string|string[] $key |
101 | * @param bool $required |
102 | * @param bool $nullable |
103 | * @return $this |
104 | */ |
105 | public function assertBool($key, $required = true, $nullable = false) |
106 | { |
107 | return $this->assertType($key, self::BOOL, $required, $nullable); |
108 | } |
109 | |
110 | /** |
111 | * @param int|int[]|string|string[] $key |
112 | * @param bool $required |
113 | * @param bool $nullable |
114 | * @return $this |
115 | */ |
116 | public function assertArray($key, $required = true, $nullable = false) |
117 | { |
118 | return $this->assertType($key, self::ARR, $required, $nullable); |
119 | } |
120 | |
121 | /** |
122 | * @param int|int[]|string|string[] $key |
123 | * @param bool $required |
124 | * @param bool $nullable |
125 | * @return $this |
126 | */ |
127 | public function assertObject($key, $required = true, $nullable = false) |
128 | { |
129 | return $this->assertType($key, self::OBJ, $required, $nullable); |
130 | } |
131 | |
132 | public function assertExists($key) |
133 | { |
134 | return $this->assertType($key, null, true, true); |
135 | } |
136 | |
137 | /** |
138 | * @param bool $allow |
139 | * @return $this |
140 | */ |
141 | public function allowExtraKeys($allow = true) |
142 | { |
143 | $this->allowExtraKeys = $allow; |
144 | return $this; |
145 | } |
146 | |
147 | public function validate($data) |
148 | { |
149 | $this->cleanResults(); |
150 | |
151 | foreach ($this->rules as $key => $rule) { |
152 | $this->validateKey($data, $key, $rule); |
153 | } |
154 | |
155 | if (!$this->allowExtraKeys) { |
156 | $this->extraKeys = array_diff(array_keys($data), array_keys($this->rules)); |
157 | } |
158 | |
159 | return $this->isValid(); |
160 | } |
161 | |
162 | public function isValid() |
163 | { |
164 | return count($this->missedKeys) === 0 && |
165 | count($this->typeMismatchKeys) === 0 && |
166 | count($this->extraKeys) === 0; |
167 | } |
168 | |
169 | /** |
170 | * @return string[] |
171 | */ |
172 | public function getMissedKeys() |
173 | { |
174 | return $this->missedKeys; |
175 | } |
176 | |
177 | /** |
178 | * Key: mismatch key name |
179 | * Value: error message |
180 | * @return string[] |
181 | */ |
182 | public function getTypeMismatchKeys() |
183 | { |
184 | return $this->typeMismatchKeys; |
185 | } |
186 | |
187 | /** |
188 | * @return string[] |
189 | */ |
190 | public function getExtraKeys() |
191 | { |
192 | return $this->extraKeys; |
193 | } |
194 | |
195 | /** |
196 | * return string |
197 | */ |
198 | public function getErrorMessage() |
199 | { |
200 | $errors = []; |
201 | if (count($this->missedKeys) > 0) { |
202 | $errors[] = 'missed keys: ' . implode(', ', $this->missedKeys); |
203 | } |
204 | foreach ($this->typeMismatchKeys as $key => $msg) { |
205 | $errors[] = $key . ' ' . $msg; |
206 | } |
207 | if (count($this->extraKeys) > 0) { |
208 | $errors[] = 'unexpected keys: ' . implode(', ', $this->extraKeys); |
209 | } |
210 | return count($errors) > 0 |
211 | ? implode('; ', $errors) |
212 | : null; |
213 | } |
214 | |
215 | private function cleanResults() |
216 | { |
217 | $this->missedKeys = $this->typeMismatchKeys = $this->extraKeys = []; |
218 | } |
219 | |
220 | /** |
221 | * @param array $data |
222 | * @param string|int $key |
223 | * @param array $rule |
224 | */ |
225 | private function validateKey($data, $key, $rule) |
226 | { |
227 | if ( |
228 | !$this->validateKeyExistence($data, $key, $rule) || |
229 | !$this->validateIfKeyTypeCanBeChecked($data, $key, $rule) |
230 | ) { |
231 | return; |
232 | } |
233 | |
234 | $type = $rule['type']; |
235 | if (!$this->isType($data[$key], $type)) { |
236 | $this->typeMismatchKeys[$key] = "is not $type"; |
237 | } |
238 | } |
239 | |
240 | /** |
241 | * @param array $data |
242 | * @param string|int $key |
243 | * @param array $rule |
244 | * @return bool should continue validation |
245 | */ |
246 | private function validateKeyExistence($data, $key, $rule) |
247 | { |
248 | if (!array_key_exists($key, $data)) { |
249 | if ($rule['req']) { |
250 | $this->missedKeys[] = $key; |
251 | } |
252 | return false; |
253 | } |
254 | return true; |
255 | } |
256 | |
257 | /** |
258 | * @param array $data |
259 | * @param string|int $key |
260 | * @param array $rule |
261 | * @return bool should continue validation |
262 | */ |
263 | private function validateIfKeyTypeCanBeChecked($data, $key, $rule) |
264 | { |
265 | if ($rule['type'] === null) { |
266 | return false; |
267 | } |
268 | |
269 | if ($data[$key] === null) { |
270 | if (!$rule['nullable']) { |
271 | $this->typeMismatchKeys[$key] = 'is null'; |
272 | } |
273 | return false; |
274 | } |
275 | return true; |
276 | } |
277 | |
278 | /** |
279 | * @param int|int[]|string|string[] $keys |
280 | * @param string|null $typeName |
281 | * @param bool $required |
282 | * @param bool $nullable |
283 | * @return $this |
284 | */ |
285 | private function assertType($keys, $typeName, $required, $nullable) |
286 | { |
287 | $keys = (array) $keys; |
288 | foreach ($keys as $key) { |
289 | $this->rules[$key] = ['type' => $typeName, 'req' => $required, 'nullable' => $nullable]; |
290 | } |
291 | return $this; |
292 | } |
293 | |
294 | /** |
295 | * @param mixed $value |
296 | * @param string $type |
297 | * @return bool |
298 | */ |
299 | private function isType($value, $type) |
300 | { |
301 | if (!isset(self::$typeCheckFunctions[$type])) { |
302 | throw new \InvalidArgumentException('Unsupported type: ' . $type); |
303 | } |
304 | $func = self::$typeCheckFunctions[$type]; |
305 | return $func($value); |
306 | } |
307 | } |