Completed
Pull Request — master (#91)
by Chad
01:14
created

Filterer::extractUses()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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 = [];
122
        $leftOverSpec = [];
123
124
        foreach ($this->specification as $field => $specification) {
125
            if (array_key_exists($field, $input)) {
126
                $inputToFilter[$field] = $input[$field];
127
                continue;
128
            }
129
130
            $leftOverSpec[$field] = $specification;
131
        }
132
133
        $leftOverInput = array_diff_key($input, $inputToFilter);
134
135
        $filteredInput = [];
136
        $errors = [];
137
        $conflicts = [];
138
        foreach ($inputToFilter as $field => $input) {
139
            $filters = $this->specification[$field];
140
            self::assertFiltersIsAnArray($filters, $field);
141
            $customError = self::validateCustomError($filters, $field);
142
            unset($filters[FilterOptions::IS_REQUIRED]);//doesn't matter if required since we have this one
143
            unset($filters[FilterOptions::DEFAULT_VALUE]);//doesn't matter if there is a default since we have a value
144
            $conflicts = self::extractConflicts($filters, $field, $conflicts);
145
            $uses = self::extractUses($filters);
146
147
            foreach ($filters as $filter) {
148
                self::assertFilterIsNotArray($filter, $field);
149
150
                if (empty($filter)) {
151
                    continue;
152
                }
153
154
                $function = array_shift($filter);
155
                $function = self::handleFilterAliases($function, $filterAliases);
156
157
                self::assertFunctionIsCallable($function, $field);
158
159
                array_unshift($filter, $input);
160
                try {
161
                    $this->addUsedInputToFilter($uses, $filteredInput, $field, $filter);
162
                    $input = call_user_func_array($function, $filter);
163
                } catch (Exception $exception) {
164
                    $errors = self::handleCustomError($field, $input, $exception, $errors, $customError);
165
                    continue 2;//next field
166
                }
167
            }
168
169
            $filteredInput[$field] = $input;
170
        }
171
172
        foreach ($leftOverSpec as $field => $filters) {
173
            self::assertFiltersIsAnArray($filters, $field);
174
            $required = self::getRequired($filters, $this->defaultRequired, $field);
175
            if (array_key_exists(FilterOptions::DEFAULT_VALUE, $filters)) {
176
                $filteredInput[$field] = $filters[FilterOptions::DEFAULT_VALUE];
177
                continue;
178
            }
179
180
            $errors = self::handleRequiredFields($required, $field, $errors);
181
        }
182
183
        $errors = self::handleAllowUnknowns($this->allowUnknowns, $leftOverInput, $errors);
184
        $errors = self::handleConflicts($filteredInput, $conflicts, $errors);
185
186
        return new FilterResponse($filteredInput, $errors, $leftOverInput);
187
    }
188
189
    /**
190
     * @return array
191
     *
192
     * @see FiltererInterface::getAliases
193
     */
194
    public function getAliases() : array
195
    {
196
        return $this->filterAliases ?? self::$registeredFilterAliases;
197
    }
198
199
    private static function extractConflicts(array &$filters, string $field, array $conflicts) : array
200
    {
201
        $conflictsWith = $filters[FilterOptions::CONFLICTS_WITH] ?? null;
202
        unset($filters[FilterOptions::CONFLICTS_WITH]);
203
        if ($conflictsWith === null) {
204
            return $conflicts;
205
        }
206
207
        if (!is_array($conflictsWith)) {
208
            $conflictsWith = [$conflictsWith];
209
        }
210
211
        $conflicts[$field] = $conflictsWith;
212
213
        return $conflicts;
214
    }
215
216
    private static function handleConflicts(array $inputToFilter, array $conflicts, array $errors)
217
    {
218
        foreach (array_keys($inputToFilter) as $field) {
219
            if (!array_key_exists($field, $conflicts)) {
220
                continue;
221
            }
222
223
            foreach ($conflicts[$field] as $conflictsWith) {
224
                if (array_key_exists($conflictsWith, $inputToFilter)) {
225
                    $errors[] = "Field '{$field}' cannot be given if field '{$conflictsWith}' is present.";
226
                }
227
            }
228
        }
229
230
        return $errors;
231
    }
232
233
    private static function extractUses(&$filters)
234
    {
235
        $uses = $filters[FilterOptions::USES] ?? [];
236
        unset($filters[FilterOptions::USES]);
237
        return is_array($uses) ? $uses : [$uses];
238
    }
239
240
    /**
241
     * @return array
242
     *
243
     * @see FiltererInterface::getSpecification
244
     */
245
    public function getSpecification() : array
246
    {
247
        return $this->specification;
248
    }
249
250
    /**
251
     * @param array $filterAliases
252
     *
253
     * @return FiltererInterface
254
     *
255
     * @see FiltererInterface::withAliases
256
     */
257
    public function withAliases(array $filterAliases) : FiltererInterface
258
    {
259
        return new Filterer($this->specification, $this->getOptions(), $filterAliases);
260
    }
261
262
    /**
263
     * @param array $specification
264
     *
265
     * @return FiltererInterface
266
     *
267
     * @see FiltererInterface::withSpecification
268
     */
269
    public function withSpecification(array $specification) : FiltererInterface
270
    {
271
        return new Filterer($specification, $this->getOptions(), $this->filterAliases);
272
    }
273
274
    /**
275
     * @return array
276
     */
277
    private function getOptions() : array
278
    {
279
        return [
280
            FiltererOptions::DEFAULT_REQUIRED => $this->defaultRequired,
281
            FiltererOptions::ALLOW_UNKNOWNS => $this->allowUnknowns,
282
        ];
283
    }
284
285
    /**
286
     * Example:
287
     * <pre>
288
     * <?php
289
     * class AppendFilter
290
     * {
291
     *     public function filter($value, $extraArg)
292
     *     {
293
     *         return $value . $extraArg;
294
     *     }
295
     * }
296
     * $appendFilter = new AppendFilter();
297
     *
298
     * $trimFunc = function($val) { return trim($val); };
299
     *
300
     * list($status, $result, $error, $unknowns) = TraderInteractive\Filterer::filter(
301
     *     [
302
     *         'field one' => [[$trimFunc], ['substr', 0, 3], [[$appendFilter, 'filter'], 'boo']],
303
     *         'field two' => ['required' => true, ['floatval']],
304
     *         'field three' => ['required' => false, ['float']],
305
     *         'field four' => ['required' => true, 'default' => 1, ['uint']],
306
     *     ],
307
     *     ['field one' => ' abcd', 'field two' => '3.14']
308
     * );
309
     *
310
     * var_dump($status);
311
     * var_dump($result);
312
     * var_dump($error);
313
     * var_dump($unknowns);
314
     * </pre>
315
     * prints:
316
     * <pre>
317
     * bool(true)
318
     * array(3) {
319
     *   'field one' =>
320
     *   string(6) "abcboo"
321
     *   'field two' =>
322
     *   double(3.14)
323
     *   'field four' =>
324
     *   int(1)
325
     * }
326
     * NULL
327
     * array(0) {
328
     * }
329
     * </pre>
330
     *
331
     * @param array $specification The specification to apply to the input.
332
     * @param array $input          The input the apply the specification to.
333
     * @param array $options        The options apply during filtering.
334
     *                              'allowUnknowns' (default false) true to allow or false to treat as error.
335
     *                              'defaultRequired' (default false) true to make fields required by default.
336
     *                              'responseType' (default RESPONSE_TYPE_ARRAY)
337
     *                                  Determines the return type, as described in the return section.
338
     *
339
     * @return array|FilterResponse If 'responseType' option is RESPONSE_TYPE_ARRAY:
340
     *                                  On success: [true, $input filtered, null, array of unknown fields]
341
     *                                  On error: [false, null, 'error message', array of unknown fields]
342
     *                              If 'responseType' option is RESPONSE_TYPE_FILTER: a FilterResponse instance
343
     *
344
     * @throws Exception
345
     * @throws InvalidArgumentException Thrown if the 'allowUnknowns' option was not a bool
346
     * @throws InvalidArgumentException Thrown if the 'defaultRequired' option was not a bool
347
     * @throws InvalidArgumentException Thrown if the 'responseType' option was not a recognized type.
348
     * @throws InvalidArgumentException Thrown if the filters for a field were not an array.
349
     * @throws InvalidArgumentException Thrown if any one filter for a field was not an array.
350
     * @throws InvalidArgumentException Thrown if the 'required' value for a field was not a bool.
351
     *
352
     * @see FiltererInterface::getSpecification For more information on specifications.
353
     */
354
    public static function filter(array $specification, array $input, array $options = [])
355
    {
356
        $options += self::DEFAULT_OPTIONS;
357
        $responseType = $options[FiltererOptions::RESPONSE_TYPE];
358
359
        $filterer = new Filterer($specification, $options);
360
        $filterResponse = $filterer->execute($input);
361
362
        return self::generateFilterResponse($responseType, $filterResponse);
363
    }
364
365
    /**
366
     * Return the filter aliases.
367
     *
368
     * @return array array where keys are aliases and values pass is_callable().
369
     */
370
    public static function getFilterAliases() : array
371
    {
372
        return self::$registeredFilterAliases;
373
    }
374
375
    /**
376
     * Set the filter aliases.
377
     *
378
     * @param array $aliases array where keys are aliases and values pass is_callable().
379
     * @return void
380
     *
381
     * @throws Exception Thrown if any of the given $aliases is not valid. @see registerAlias()
382
     */
383
    public static function setFilterAliases(array $aliases)
384
    {
385
        $originalAliases = self::$registeredFilterAliases;
386
        self::$registeredFilterAliases = [];
387
        try {
388
            foreach ($aliases as $alias => $callback) {
389
                self::registerAlias($alias, $callback);
390
            }
391
        } catch (Throwable $throwable) {
392
            self::$registeredFilterAliases = $originalAliases;
393
            throw $throwable;
394
        }
395
    }
396
397
    /**
398
     * Register a new alias with the Filterer
399
     *
400
     * @param string|int $alias the alias to register
401
     * @param callable $filter the aliased callable filter
402
     * @param bool $overwrite Flag to overwrite existing alias if it exists
403
     *
404
     * @return void
405
     *
406
     * @throws \InvalidArgumentException if $alias was not a string or int
407
     * @throws Exception if $overwrite is false and $alias exists
408
     */
409
    public static function registerAlias($alias, callable $filter, bool $overwrite = false)
410
    {
411
        self::assertIfStringOrInt($alias);
412
        self::assertIfAliasExists($alias, $overwrite);
413
        self::$registeredFilterAliases[$alias] = $filter;
414
    }
415
416
    /**
417
     * Filter an array by applying filters to each member
418
     *
419
     * @param array $values an array to be filtered. Use the Arrays::filter() before this method to ensure counts when
420
     *                      you pass into Filterer
421
     * @param array $filters filters with each specified the same as in @see self::filter.
422
     *                       Eg [['string', false, 2], ['uint']]
423
     *
424
     * @return array the filtered $values
425
     *
426
     * @throws FilterException if any member of $values fails filtering
427
     */
428
    public static function ofScalars(array $values, array $filters) : array
429
    {
430
        $wrappedFilters = [];
431
        foreach ($values as $key => $item) {
432
            $wrappedFilters[$key] = $filters;
433
        }
434
435
        list($status, $result, $error) = self::filter($wrappedFilters, $values);
436
        if (!$status) {
437
            throw new FilterException($error);
438
        }
439
440
        return $result;
441
    }
442
443
    /**
444
     * Filter an array by applying filters to each member
445
     *
446
     * @param array $values as array to be filtered. Use the Arrays::filter() before this method to ensure counts when
447
     *                      you pass into Filterer
448
     * @param array $spec spec to apply to each $values member, specified the same as in @see self::filter.
449
     *     Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...]
450
     *
451
     * @return array the filtered $values
452
     *
453
     * @throws Exception if any member of $values fails filtering
454
     */
455
    public static function ofArrays(array $values, array $spec) : array
456
    {
457
        $results = [];
458
        $errors = [];
459
        foreach ($values as $key => $item) {
460
            if (!is_array($item)) {
461
                $errors[] = "Value at position '{$key}' was not an array";
462
                continue;
463
            }
464
465
            list($status, $result, $error) = self::filter($spec, $item);
466
            if (!$status) {
467
                $errors[] = $error;
468
                continue;
469
            }
470
471
            $results[$key] = $result;
472
        }
473
474
        if (!empty($errors)) {
475
            throw new FilterException(implode("\n", $errors));
476
        }
477
478
        return $results;
479
    }
480
481
    /**
482
     * Filter $value by using a Filterer $spec and Filterer's default options.
483
     *
484
     * @param array $value array to be filtered. Use the Arrays::filter() before this method to ensure counts when you
485
     *                     pass into Filterer
486
     * @param array $spec spec to apply to $value, specified the same as in @see self::filter.
487
     *     Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...]
488
     *
489
     * @return array the filtered $value
490
     *
491
     * @throws FilterException if $value fails filtering
492
     */
493
    public static function ofArray(array $value, array $spec) : array
494
    {
495
        list($status, $result, $error) = self::filter($spec, $value);
496
        if (!$status) {
497
            throw new FilterException($error);
498
        }
499
500
        return $result;
501
    }
502
503
    private static function assertIfStringOrInt($alias)
504
    {
505
        if (!is_string($alias) && !is_int($alias)) {
506
            throw new InvalidArgumentException('$alias was not a string or int');
507
        }
508
    }
509
510
    private static function assertIfAliasExists($alias, bool $overwrite)
511
    {
512
        if (array_key_exists($alias, self::$registeredFilterAliases) && !$overwrite) {
513
            throw new Exception("Alias '{$alias}' exists");
514
        }
515
    }
516
517
    private static function checkForUnknowns(array $leftOverInput, array $errors) : array
518
    {
519
        foreach ($leftOverInput as $field => $value) {
520
            $errors[$field] = "Field '{$field}' with value '" . trim(var_export($value, true), "'") . "' is unknown";
521
        }
522
523
        return $errors;
524
    }
525
526
    private static function handleAllowUnknowns(bool $allowUnknowns, array $leftOverInput, array $errors) : array
527
    {
528
        if (!$allowUnknowns) {
529
            $errors = self::checkForUnknowns($leftOverInput, $errors);
530
        }
531
532
        return $errors;
533
    }
534
535
    private static function handleRequiredFields(bool $required, string $field, array $errors) : array
536
    {
537
        if ($required) {
538
            $errors[$field] = "Field '{$field}' was required and not present";
539
        }
540
        return $errors;
541
    }
542
543
    private static function getRequired($filters, $defaultRequired, $field) : bool
544
    {
545
        $required = $filters[FilterOptions::IS_REQUIRED] ?? $defaultRequired;
546
        if ($required !== false && $required !== true) {
547
            throw new InvalidArgumentException(
548
                sprintf("'%s' for field '%s' was not a bool", FilterOptions::IS_REQUIRED, $field)
549
            );
550
        }
551
552
        return $required;
553
    }
554
555
    private static function assertFiltersIsAnArray($filters, string $field)
556
    {
557
        if (!is_array($filters)) {
558
            throw new InvalidArgumentException("filters for field '{$field}' was not a array");
559
        }
560
    }
561
562
    private static function handleCustomError(
563
        string $field,
564
        $value,
565
        Throwable $e,
566
        array $errors,
567
        string $customError = null
568
    ) : array {
569
        $error = $customError;
570
        if ($error === null) {
571
            $errorFormat = "Field '%s' with value '{value}' failed filtering, message '%s'";
572
            $error = sprintf($errorFormat, $field, $e->getMessage());
573
        }
574
575
        $errors[$field] = str_replace('{value}', trim(var_export($value, true), "'"), $error);
576
        return $errors;
577
    }
578
579
    private static function assertFunctionIsCallable($function, string $field)
580
    {
581
        if (!is_callable($function)) {
582
            throw new Exception(
583
                "Function '" . trim(var_export($function, true), "'") . "' for field '{$field}' is not callable"
584
            );
585
        }
586
    }
587
588
    private static function handleFilterAliases($function, $filterAliases)
589
    {
590
        if ((is_string($function) || is_int($function)) && array_key_exists($function, $filterAliases)) {
591
            $function = $filterAliases[$function];
592
        }
593
594
        return $function;
595
    }
596
597
    private static function assertFilterIsNotArray($filter, string $field)
598
    {
599
        if (!is_array($filter)) {
600
            throw new InvalidArgumentException("filter for field '{$field}' was not a array");
601
        }
602
    }
603
604
    private static function validateCustomError(array &$filters, string $field)
605
    {
606
        $customError = null;
607
        if (array_key_exists(FilterOptions::CUSTOM_ERROR, $filters)) {
608
            $customError = $filters[FilterOptions::CUSTOM_ERROR];
609
            if (!is_string($customError) || trim($customError) === '') {
610
                throw new InvalidArgumentException(
611
                    sprintf("%s for field '%s' was not a non-empty string", FilterOptions::CUSTOM_ERROR, $field)
612
                );
613
            }
614
615
            unset($filters[FilterOptions::CUSTOM_ERROR]);//unset so its not used as a filter
616
        }
617
618
        return $customError;
619
    }
620
621 View Code Duplication
    private static function getAllowUnknowns(array $options) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
622
    {
623
        $allowUnknowns = $options[FiltererOptions::ALLOW_UNKNOWNS];
624
        if ($allowUnknowns !== false && $allowUnknowns !== true) {
625
            throw new InvalidArgumentException(sprintf("'%s' option was not a bool", FiltererOptions::ALLOW_UNKNOWNS));
626
        }
627
628
        return $allowUnknowns;
629
    }
630
631 View Code Duplication
    private static function getDefaultRequired(array $options) : bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
632
    {
633
        $defaultRequired = $options[FiltererOptions::DEFAULT_REQUIRED];
634
        if ($defaultRequired !== false && $defaultRequired !== true) {
635
            throw new InvalidArgumentException(
636
                sprintf("'%s' option was not a bool", FiltererOptions::DEFAULT_REQUIRED)
637
            );
638
        }
639
640
        return $defaultRequired;
641
    }
642
643
    /**
644
     * @param string         $responseType   The type of object that should be returned.
645
     * @param FilterResponse $filterResponse The filter response to generate the typed response from.
646
     *
647
     * @return array|FilterResponse
648
     *
649
     * @see filter For more information on how responseType is handled and returns are structured.
650
     */
651
    private static function generateFilterResponse(string $responseType, FilterResponse $filterResponse)
652
    {
653
        if ($responseType === self::RESPONSE_TYPE_FILTER) {
654
            return $filterResponse;
655
        }
656
657
        if ($responseType === self::RESPONSE_TYPE_ARRAY) {
658
            return [
659
                $filterResponse->success,
660
                $filterResponse->success ? $filterResponse->filteredValue : null,
661
                $filterResponse->errorMessage,
662
                $filterResponse->unknowns
663
            ];
664
        }
665
666
        throw new InvalidArgumentException(sprintf("'%s' was not a recognized value", FiltererOptions::RESPONSE_TYPE));
667
    }
668
669
    private function addUsedInputToFilter(array $uses, array $filteredInput, string $field, array &$filter)
670
    {
671
        foreach ($uses as $usedField) {
672
            if (array_key_exists($usedField, $filteredInput)) {
673
                array_push($filter, $filteredInput[$usedField]);
674
                continue;
675
            }
676
677
            throw new FilterException(
678
                "{$field} uses {$usedField} but {$usedField} was not given."
679
            );
680
        }
681
    }
682
}
683