1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace TraderInteractive; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
use InvalidArgumentException; |
7
|
|
|
use Throwable; |
8
|
|
|
use TraderInteractive\Exceptions\FilterException; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Class to filter an array of input. |
12
|
|
|
*/ |
13
|
|
|
final class Filterer implements FiltererInterface |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* @var array |
17
|
|
|
*/ |
18
|
|
|
const DEFAULT_FILTER_ALIASES = [ |
19
|
|
|
'array' => '\\TraderInteractive\\Filter\\Arrays::filter', |
20
|
|
|
'arrayize' => '\\TraderInteractive\\Filter\\Arrays::arrayize', |
21
|
|
|
'bool' => '\\TraderInteractive\\Filter\\Booleans::filter', |
22
|
|
|
'bool-convert' => '\\TraderInteractive\\Filter\\Booleans::convert', |
23
|
|
|
'compress-string' => '\\TraderInteractive\\Filter\\Strings::compress', |
24
|
|
|
'concat' => '\\TraderInteractive\\Filter\\Strings::concat', |
25
|
|
|
'date' => '\\TraderInteractive\\Filter\\DateTime::filter', |
26
|
|
|
'date-format' => '\\TraderInteractive\\Filter\\DateTime::format', |
27
|
|
|
'email' => '\\TraderInteractive\\Filter\\Email::filter', |
28
|
|
|
'explode' => '\\TraderInteractive\\Filter\\Strings::explode', |
29
|
|
|
'flatten' => '\\TraderInteractive\\Filter\\Arrays::flatten', |
30
|
|
|
'float' => '\\TraderInteractive\\Filter\\Floats::filter', |
31
|
|
|
'in' => '\\TraderInteractive\\Filter\\Arrays::in', |
32
|
|
|
'int' => '\\TraderInteractive\\Filter\\Ints::filter', |
33
|
|
|
'ofArray' => '\\TraderInteractive\\Filterer::ofArray', |
34
|
|
|
'ofArrays' => '\\TraderInteractive\\Filterer::ofArrays', |
35
|
|
|
'ofScalars' => '\\TraderInteractive\\Filterer::ofScalars', |
36
|
|
|
'redact' => '\\TraderInteractive\\Filter\\Strings::redact', |
37
|
|
|
'string' => '\\TraderInteractive\\Filter\\Strings::filter', |
38
|
|
|
'strip-tags' => '\\TraderInteractive\\Filter\\Strings::stripTags', |
39
|
|
|
'timezone' => '\\TraderInteractive\\Filter\\DateTimeZone::filter', |
40
|
|
|
'translate' => '\\TraderInteractive\\Filter\\Strings::translate', |
41
|
|
|
'uint' => '\\TraderInteractive\\Filter\\UnsignedInt::filter', |
42
|
|
|
'url' => '\\TraderInteractive\\Filter\\Url::filter', |
43
|
|
|
]; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
const DEFAULT_OPTIONS = [ |
49
|
|
|
FiltererOptions::ALLOW_UNKNOWNS => false, |
50
|
|
|
FiltererOptions::DEFAULT_REQUIRED => false, |
51
|
|
|
FiltererOptions::RESPONSE_TYPE => self::RESPONSE_TYPE_ARRAY, |
52
|
|
|
]; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string |
56
|
|
|
*/ |
57
|
|
|
const RESPONSE_TYPE_ARRAY = 'array'; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var string |
61
|
|
|
*/ |
62
|
|
|
const RESPONSE_TYPE_FILTER = FilterResponse::class; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var array |
66
|
|
|
*/ |
67
|
|
|
private static $registeredFilterAliases = self::DEFAULT_FILTER_ALIASES; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @var array|null |
71
|
|
|
*/ |
72
|
|
|
private $filterAliases; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @var array |
76
|
|
|
*/ |
77
|
|
|
private $specification; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var bool |
81
|
|
|
*/ |
82
|
|
|
private $allowUnknowns; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @var bool |
86
|
|
|
*/ |
87
|
|
|
private $defaultRequired; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @param array $specification The specification to apply to the value. |
91
|
|
|
* @param array $options The options apply during filtering. |
92
|
|
|
* 'allowUnknowns' (default false) true to allow or false to treat as error. |
93
|
|
|
* 'defaultRequired' (default false) true to make fields required by default. |
94
|
|
|
* @param array|null $filterAliases The filter aliases to accept. |
95
|
|
|
* |
96
|
|
|
* @throws InvalidArgumentException if 'allowUnknowns' option was not a bool |
97
|
|
|
* @throws InvalidArgumentException if 'defaultRequired' option was not a bool |
98
|
|
|
*/ |
99
|
|
|
public function __construct(array $specification, array $options = [], array $filterAliases = null) |
100
|
|
|
{ |
101
|
|
|
$options += self::DEFAULT_OPTIONS; |
102
|
|
|
|
103
|
|
|
$this->specification = $specification; |
104
|
|
|
$this->filterAliases = $filterAliases; |
105
|
|
|
$this->allowUnknowns = self::getAllowUnknowns($options); |
106
|
|
|
$this->defaultRequired = self::getDefaultRequired($options); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @param mixed $input The input to filter. |
111
|
|
|
* |
112
|
|
|
* @return FilterResponse |
113
|
|
|
* |
114
|
|
|
* @throws InvalidArgumentException Thrown if the filters for a field were not an array. |
115
|
|
|
* @throws InvalidArgumentException Thrown if any one filter for a field was not an array. |
116
|
|
|
* @throws InvalidArgumentException Thrown if the 'required' value for a field was not a bool. |
117
|
|
|
*/ |
118
|
|
|
public function execute(array $input) : FilterResponse |
119
|
|
|
{ |
120
|
|
|
$filterAliases = $this->getAliases(); |
121
|
|
|
$inputToFilter = array_intersect_key($input, $this->specification); |
122
|
|
|
$leftOverSpec = array_diff_key($this->specification, $input); |
123
|
|
|
$leftOverInput = array_diff_key($input, $this->specification); |
124
|
|
|
|
125
|
|
|
$errors = []; |
126
|
|
|
$conflicts = []; |
127
|
|
|
foreach ($inputToFilter as $field => $input) { |
128
|
|
|
$filters = $this->specification[$field]; |
129
|
|
|
self::assertFiltersIsAnArray($filters, $field); |
130
|
|
|
$customError = self::validateCustomError($filters, $field); |
131
|
|
|
unset($filters[FilterOptions::IS_REQUIRED]);//doesn't matter if required since we have this one |
132
|
|
|
unset($filters[FilterOptions::DEFAULT_VALUE]);//doesn't matter if there is a default since we have a value |
133
|
|
|
$conflicts = self::extractConflicts($filters, $field, $conflicts); |
134
|
|
|
|
135
|
|
|
foreach ($filters as $filter) { |
136
|
|
|
self::assertFilterIsNotArray($filter, $field); |
137
|
|
|
|
138
|
|
|
if (empty($filter)) { |
139
|
|
|
continue; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$function = array_shift($filter); |
143
|
|
|
$function = self::handleFilterAliases($function, $filterAliases); |
144
|
|
|
|
145
|
|
|
self::assertFunctionIsCallable($function, $field); |
146
|
|
|
|
147
|
|
|
array_unshift($filter, $input); |
148
|
|
|
try { |
149
|
|
|
$input = call_user_func_array($function, $filter); |
150
|
|
|
} catch (Exception $exception) { |
151
|
|
|
$errors = self::handleCustomError($field, $input, $exception, $errors, $customError); |
152
|
|
|
continue 2;//next field |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$inputToFilter[$field] = $input; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
foreach ($leftOverSpec as $field => $filters) { |
160
|
|
|
self::assertFiltersIsAnArray($filters, $field); |
161
|
|
|
$required = self::getRequired($filters, $this->defaultRequired, $field); |
162
|
|
|
if (array_key_exists(FilterOptions::DEFAULT_VALUE, $filters)) { |
163
|
|
|
$inputToFilter[$field] = $filters[FilterOptions::DEFAULT_VALUE]; |
164
|
|
|
continue; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$errors = self::handleRequiredFields($required, $field, $errors); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$errors = self::handleAllowUnknowns($this->allowUnknowns, $leftOverInput, $errors); |
171
|
|
|
$errors = self::handleConflicts($inputToFilter, $conflicts, $errors); |
172
|
|
|
|
173
|
|
|
return new FilterResponse($inputToFilter, $errors, $leftOverInput); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @return array |
178
|
|
|
* |
179
|
|
|
* @see FiltererInterface::getAliases |
180
|
|
|
*/ |
181
|
|
|
public function getAliases() : array |
182
|
|
|
{ |
183
|
|
|
return $this->filterAliases ?? self::$registeredFilterAliases; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
private static function extractConflicts(array &$filters, string $field, array $conflicts) : array |
187
|
|
|
{ |
188
|
|
|
$conflictsWith = $filters[FilterOptions::CONFLICTS_WITH] ?? null; |
189
|
|
|
unset($filters[FilterOptions::CONFLICTS_WITH]); |
190
|
|
|
if ($conflictsWith === null) { |
191
|
|
|
return $conflicts; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
if (!is_array($conflictsWith)) { |
195
|
|
|
$conflictsWith = [$conflictsWith]; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$conflicts[$field] = $conflictsWith; |
199
|
|
|
|
200
|
|
|
return $conflicts; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
private static function handleConflicts(array $inputToFilter, array $conflicts, array $errors) |
204
|
|
|
{ |
205
|
|
|
foreach (array_keys($inputToFilter) as $field) { |
206
|
|
|
if (!array_key_exists($field, $conflicts)) { |
207
|
|
|
continue; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
foreach ($conflicts[$field] as $conflictsWith) { |
211
|
|
|
if (array_key_exists($conflictsWith, $inputToFilter)) { |
212
|
|
|
$errors[] = "Field '{$field}' cannot be given if field '{$conflictsWith}' is present."; |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $errors; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @return array |
222
|
|
|
* |
223
|
|
|
* @see FiltererInterface::getSpecification |
224
|
|
|
*/ |
225
|
|
|
public function getSpecification() : array |
226
|
|
|
{ |
227
|
|
|
return $this->specification; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @param array $filterAliases |
232
|
|
|
* |
233
|
|
|
* @return FiltererInterface |
234
|
|
|
* |
235
|
|
|
* @see FiltererInterface::withAliases |
236
|
|
|
*/ |
237
|
|
|
public function withAliases(array $filterAliases) : FiltererInterface |
238
|
|
|
{ |
239
|
|
|
return new Filterer($this->specification, $this->getOptions(), $filterAliases); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @param array $specification |
244
|
|
|
* |
245
|
|
|
* @return FiltererInterface |
246
|
|
|
* |
247
|
|
|
* @see FiltererInterface::withSpecification |
248
|
|
|
*/ |
249
|
|
|
public function withSpecification(array $specification) : FiltererInterface |
250
|
|
|
{ |
251
|
|
|
return new Filterer($specification, $this->getOptions(), $this->filterAliases); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @return array |
256
|
|
|
*/ |
257
|
|
|
private function getOptions() : array |
258
|
|
|
{ |
259
|
|
|
return [ |
260
|
|
|
FiltererOptions::DEFAULT_REQUIRED => $this->defaultRequired, |
261
|
|
|
FiltererOptions::ALLOW_UNKNOWNS => $this->allowUnknowns, |
262
|
|
|
]; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Example: |
267
|
|
|
* <pre> |
268
|
|
|
* <?php |
269
|
|
|
* class AppendFilter |
270
|
|
|
* { |
271
|
|
|
* public function filter($value, $extraArg) |
272
|
|
|
* { |
273
|
|
|
* return $value . $extraArg; |
274
|
|
|
* } |
275
|
|
|
* } |
276
|
|
|
* $appendFilter = new AppendFilter(); |
277
|
|
|
* |
278
|
|
|
* $trimFunc = function($val) { return trim($val); }; |
279
|
|
|
* |
280
|
|
|
* list($status, $result, $error, $unknowns) = TraderInteractive\Filterer::filter( |
281
|
|
|
* [ |
282
|
|
|
* 'field one' => [[$trimFunc], ['substr', 0, 3], [[$appendFilter, 'filter'], 'boo']], |
283
|
|
|
* 'field two' => ['required' => true, ['floatval']], |
284
|
|
|
* 'field three' => ['required' => false, ['float']], |
285
|
|
|
* 'field four' => ['required' => true, 'default' => 1, ['uint']], |
286
|
|
|
* ], |
287
|
|
|
* ['field one' => ' abcd', 'field two' => '3.14'] |
288
|
|
|
* ); |
289
|
|
|
* |
290
|
|
|
* var_dump($status); |
291
|
|
|
* var_dump($result); |
292
|
|
|
* var_dump($error); |
293
|
|
|
* var_dump($unknowns); |
294
|
|
|
* </pre> |
295
|
|
|
* prints: |
296
|
|
|
* <pre> |
297
|
|
|
* bool(true) |
298
|
|
|
* array(3) { |
299
|
|
|
* 'field one' => |
300
|
|
|
* string(6) "abcboo" |
301
|
|
|
* 'field two' => |
302
|
|
|
* double(3.14) |
303
|
|
|
* 'field four' => |
304
|
|
|
* int(1) |
305
|
|
|
* } |
306
|
|
|
* NULL |
307
|
|
|
* array(0) { |
308
|
|
|
* } |
309
|
|
|
* </pre> |
310
|
|
|
* |
311
|
|
|
* @param array $specification The specification to apply to the input. |
312
|
|
|
* @param array $input The input the apply the specification to. |
313
|
|
|
* @param array $options The options apply during filtering. |
314
|
|
|
* 'allowUnknowns' (default false) true to allow or false to treat as error. |
315
|
|
|
* 'defaultRequired' (default false) true to make fields required by default. |
316
|
|
|
* 'responseType' (default RESPONSE_TYPE_ARRAY) |
317
|
|
|
* Determines the return type, as described in the return section. |
318
|
|
|
* |
319
|
|
|
* @return array|FilterResponse If 'responseType' option is RESPONSE_TYPE_ARRAY: |
320
|
|
|
* On success: [true, $input filtered, null, array of unknown fields] |
321
|
|
|
* On error: [false, null, 'error message', array of unknown fields] |
322
|
|
|
* If 'responseType' option is RESPONSE_TYPE_FILTER: a FilterResponse instance |
323
|
|
|
* |
324
|
|
|
* @throws Exception |
325
|
|
|
* @throws InvalidArgumentException Thrown if the 'allowUnknowns' option was not a bool |
326
|
|
|
* @throws InvalidArgumentException Thrown if the 'defaultRequired' option was not a bool |
327
|
|
|
* @throws InvalidArgumentException Thrown if the 'responseType' option was not a recognized type. |
328
|
|
|
* @throws InvalidArgumentException Thrown if the filters for a field were not an array. |
329
|
|
|
* @throws InvalidArgumentException Thrown if any one filter for a field was not an array. |
330
|
|
|
* @throws InvalidArgumentException Thrown if the 'required' value for a field was not a bool. |
331
|
|
|
* |
332
|
|
|
* @see FiltererInterface::getSpecification For more information on specifications. |
333
|
|
|
*/ |
334
|
|
|
public static function filter(array $specification, array $input, array $options = []) |
335
|
|
|
{ |
336
|
|
|
$options += self::DEFAULT_OPTIONS; |
337
|
|
|
$responseType = $options[FiltererOptions::RESPONSE_TYPE]; |
338
|
|
|
|
339
|
|
|
$filterer = new Filterer($specification, $options); |
340
|
|
|
$filterResponse = $filterer->execute($input); |
341
|
|
|
|
342
|
|
|
return self::generateFilterResponse($responseType, $filterResponse); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Return the filter aliases. |
347
|
|
|
* |
348
|
|
|
* @return array array where keys are aliases and values pass is_callable(). |
349
|
|
|
*/ |
350
|
|
|
public static function getFilterAliases() : array |
351
|
|
|
{ |
352
|
|
|
return self::$registeredFilterAliases; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Set the filter aliases. |
357
|
|
|
* |
358
|
|
|
* @param array $aliases array where keys are aliases and values pass is_callable(). |
359
|
|
|
* @return void |
360
|
|
|
* |
361
|
|
|
* @throws Exception Thrown if any of the given $aliases is not valid. @see registerAlias() |
362
|
|
|
*/ |
363
|
|
|
public static function setFilterAliases(array $aliases) |
364
|
|
|
{ |
365
|
|
|
$originalAliases = self::$registeredFilterAliases; |
366
|
|
|
self::$registeredFilterAliases = []; |
367
|
|
|
try { |
368
|
|
|
foreach ($aliases as $alias => $callback) { |
369
|
|
|
self::registerAlias($alias, $callback); |
370
|
|
|
} |
371
|
|
|
} catch (Throwable $throwable) { |
372
|
|
|
self::$registeredFilterAliases = $originalAliases; |
373
|
|
|
throw $throwable; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Register a new alias with the Filterer |
379
|
|
|
* |
380
|
|
|
* @param string|int $alias the alias to register |
381
|
|
|
* @param callable $filter the aliased callable filter |
382
|
|
|
* @param bool $overwrite Flag to overwrite existing alias if it exists |
383
|
|
|
* |
384
|
|
|
* @return void |
385
|
|
|
* |
386
|
|
|
* @throws \InvalidArgumentException if $alias was not a string or int |
387
|
|
|
* @throws Exception if $overwrite is false and $alias exists |
388
|
|
|
*/ |
389
|
|
|
public static function registerAlias($alias, callable $filter, bool $overwrite = false) |
390
|
|
|
{ |
391
|
|
|
self::assertIfStringOrInt($alias); |
392
|
|
|
self::assertIfAliasExists($alias, $overwrite); |
393
|
|
|
self::$registeredFilterAliases[$alias] = $filter; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Filter an array by applying filters to each member |
398
|
|
|
* |
399
|
|
|
* @param array $values an array to be filtered. Use the Arrays::filter() before this method to ensure counts when |
400
|
|
|
* you pass into Filterer |
401
|
|
|
* @param array $filters filters with each specified the same as in @see self::filter. |
402
|
|
|
* Eg [['string', false, 2], ['uint']] |
403
|
|
|
* |
404
|
|
|
* @return array the filtered $values |
405
|
|
|
* |
406
|
|
|
* @throws FilterException if any member of $values fails filtering |
407
|
|
|
*/ |
408
|
|
|
public static function ofScalars(array $values, array $filters) : array |
409
|
|
|
{ |
410
|
|
|
$wrappedFilters = []; |
411
|
|
|
foreach ($values as $key => $item) { |
412
|
|
|
$wrappedFilters[$key] = $filters; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
list($status, $result, $error) = self::filter($wrappedFilters, $values); |
416
|
|
|
if (!$status) { |
417
|
|
|
throw new FilterException($error); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
return $result; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Filter an array by applying filters to each member |
425
|
|
|
* |
426
|
|
|
* @param array $values as array to be filtered. Use the Arrays::filter() before this method to ensure counts when |
427
|
|
|
* you pass into Filterer |
428
|
|
|
* @param array $spec spec to apply to each $values member, specified the same as in @see self::filter. |
429
|
|
|
* Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...] |
430
|
|
|
* |
431
|
|
|
* @return array the filtered $values |
432
|
|
|
* |
433
|
|
|
* @throws Exception if any member of $values fails filtering |
434
|
|
|
*/ |
435
|
|
|
public static function ofArrays(array $values, array $spec) : array |
436
|
|
|
{ |
437
|
|
|
$results = []; |
438
|
|
|
$errors = []; |
439
|
|
|
foreach ($values as $key => $item) { |
440
|
|
|
if (!is_array($item)) { |
441
|
|
|
$errors[] = "Value at position '{$key}' was not an array"; |
442
|
|
|
continue; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
list($status, $result, $error) = self::filter($spec, $item); |
446
|
|
|
if (!$status) { |
447
|
|
|
$errors[] = $error; |
448
|
|
|
continue; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
$results[$key] = $result; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
if (!empty($errors)) { |
455
|
|
|
throw new FilterException(implode("\n", $errors)); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
return $results; |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Filter $value by using a Filterer $spec and Filterer's default options. |
463
|
|
|
* |
464
|
|
|
* @param array $value array to be filtered. Use the Arrays::filter() before this method to ensure counts when you |
465
|
|
|
* pass into Filterer |
466
|
|
|
* @param array $spec spec to apply to $value, specified the same as in @see self::filter. |
467
|
|
|
* Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...] |
468
|
|
|
* |
469
|
|
|
* @return array the filtered $value |
470
|
|
|
* |
471
|
|
|
* @throws FilterException if $value fails filtering |
472
|
|
|
*/ |
473
|
|
|
public static function ofArray(array $value, array $spec) : array |
474
|
|
|
{ |
475
|
|
|
list($status, $result, $error) = self::filter($spec, $value); |
476
|
|
|
if (!$status) { |
477
|
|
|
throw new FilterException($error); |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
return $result; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
private static function assertIfStringOrInt($alias) |
484
|
|
|
{ |
485
|
|
|
if (!is_string($alias) && !is_int($alias)) { |
486
|
|
|
throw new InvalidArgumentException('$alias was not a string or int'); |
487
|
|
|
} |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
private static function assertIfAliasExists($alias, bool $overwrite) |
491
|
|
|
{ |
492
|
|
|
if (array_key_exists($alias, self::$registeredFilterAliases) && !$overwrite) { |
493
|
|
|
throw new Exception("Alias '{$alias}' exists"); |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
private static function checkForUnknowns(array $leftOverInput, array $errors) : array |
498
|
|
|
{ |
499
|
|
|
foreach ($leftOverInput as $field => $value) { |
500
|
|
|
$errors[$field] = "Field '{$field}' with value '" . trim(var_export($value, true), "'") . "' is unknown"; |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
return $errors; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
private static function handleAllowUnknowns(bool $allowUnknowns, array $leftOverInput, array $errors) : array |
507
|
|
|
{ |
508
|
|
|
if (!$allowUnknowns) { |
509
|
|
|
$errors = self::checkForUnknowns($leftOverInput, $errors); |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
return $errors; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
private static function handleRequiredFields(bool $required, string $field, array $errors) : array |
516
|
|
|
{ |
517
|
|
|
if ($required) { |
518
|
|
|
$errors[$field] = "Field '{$field}' was required and not present"; |
519
|
|
|
} |
520
|
|
|
return $errors; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
private static function getRequired($filters, $defaultRequired, $field) : bool |
524
|
|
|
{ |
525
|
|
|
$required = $filters[FilterOptions::IS_REQUIRED] ?? $defaultRequired; |
526
|
|
|
if ($required !== false && $required !== true) { |
527
|
|
|
throw new InvalidArgumentException( |
528
|
|
|
sprintf("'%s' for field '%s' was not a bool", FilterOptions::IS_REQUIRED, $field) |
529
|
|
|
); |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
return $required; |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
private static function assertFiltersIsAnArray($filters, string $field) |
536
|
|
|
{ |
537
|
|
|
if (!is_array($filters)) { |
538
|
|
|
throw new InvalidArgumentException("filters for field '{$field}' was not a array"); |
539
|
|
|
} |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
private static function handleCustomError( |
543
|
|
|
string $field, |
544
|
|
|
$value, |
545
|
|
|
Throwable $e, |
546
|
|
|
array $errors, |
547
|
|
|
string $customError = null |
548
|
|
|
) : array { |
549
|
|
|
$error = $customError; |
550
|
|
|
if ($error === null) { |
551
|
|
|
$errorFormat = "Field '%s' with value '{value}' failed filtering, message '%s'"; |
552
|
|
|
$error = sprintf($errorFormat, $field, $e->getMessage()); |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
$errors[$field] = str_replace('{value}', trim(var_export($value, true), "'"), $error); |
556
|
|
|
return $errors; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
private static function assertFunctionIsCallable($function, string $field) |
560
|
|
|
{ |
561
|
|
|
if (!is_callable($function)) { |
562
|
|
|
throw new Exception( |
563
|
|
|
"Function '" . trim(var_export($function, true), "'") . "' for field '{$field}' is not callable" |
564
|
|
|
); |
565
|
|
|
} |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
private static function handleFilterAliases($function, $filterAliases) |
569
|
|
|
{ |
570
|
|
|
if ((is_string($function) || is_int($function)) && array_key_exists($function, $filterAliases)) { |
571
|
|
|
$function = $filterAliases[$function]; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
return $function; |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
private static function assertFilterIsNotArray($filter, string $field) |
578
|
|
|
{ |
579
|
|
|
if (!is_array($filter)) { |
580
|
|
|
throw new InvalidArgumentException("filter for field '{$field}' was not a array"); |
581
|
|
|
} |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
private static function validateCustomError(array &$filters, string $field) |
585
|
|
|
{ |
586
|
|
|
$customError = null; |
587
|
|
|
if (array_key_exists(FilterOptions::CUSTOM_ERROR, $filters)) { |
588
|
|
|
$customError = $filters[FilterOptions::CUSTOM_ERROR]; |
589
|
|
|
if (!is_string($customError) || trim($customError) === '') { |
590
|
|
|
throw new InvalidArgumentException( |
591
|
|
|
sprintf("%s for field '%s' was not a non-empty string", FilterOptions::CUSTOM_ERROR, $field) |
592
|
|
|
); |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
unset($filters[FilterOptions::CUSTOM_ERROR]);//unset so its not used as a filter |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
return $customError; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
View Code Duplication |
private static function getAllowUnknowns(array $options) : bool |
|
|
|
|
602
|
|
|
{ |
603
|
|
|
$allowUnknowns = $options[FiltererOptions::ALLOW_UNKNOWNS]; |
604
|
|
|
if ($allowUnknowns !== false && $allowUnknowns !== true) { |
605
|
|
|
throw new InvalidArgumentException(sprintf("'%s' option was not a bool", FiltererOptions::ALLOW_UNKNOWNS)); |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
return $allowUnknowns; |
609
|
|
|
} |
610
|
|
|
|
611
|
|
View Code Duplication |
private static function getDefaultRequired(array $options) : bool |
|
|
|
|
612
|
|
|
{ |
613
|
|
|
$defaultRequired = $options[FiltererOptions::DEFAULT_REQUIRED]; |
614
|
|
|
if ($defaultRequired !== false && $defaultRequired !== true) { |
615
|
|
|
throw new InvalidArgumentException( |
616
|
|
|
sprintf("'%s' option was not a bool", FiltererOptions::DEFAULT_REQUIRED) |
617
|
|
|
); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
return $defaultRequired; |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* @param string $responseType The type of object that should be returned. |
625
|
|
|
* @param FilterResponse $filterResponse The filter response to generate the typed response from. |
626
|
|
|
* |
627
|
|
|
* @return array|FilterResponse |
628
|
|
|
* |
629
|
|
|
* @see filter For more information on how responseType is handled and returns are structured. |
630
|
|
|
*/ |
631
|
|
|
private static function generateFilterResponse(string $responseType, FilterResponse $filterResponse) |
632
|
|
|
{ |
633
|
|
|
if ($responseType === self::RESPONSE_TYPE_FILTER) { |
634
|
|
|
return $filterResponse; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
if ($responseType === self::RESPONSE_TYPE_ARRAY) { |
638
|
|
|
return [ |
639
|
|
|
$filterResponse->success, |
640
|
|
|
$filterResponse->success ? $filterResponse->filteredValue : null, |
641
|
|
|
$filterResponse->errorMessage, |
642
|
|
|
$filterResponse->unknowns |
643
|
|
|
]; |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
throw new InvalidArgumentException(sprintf("'%s' was not a recognized value", FiltererOptions::RESPONSE_TYPE)); |
647
|
|
|
} |
648
|
|
|
} |
649
|
|
|
|
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.