Completed
Push — master ( 047bc0...3590bf )
by Chad
15s queued 11s
created

Filterer::validateReturnOnNull()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17

Duplication

Lines 17
Ratio 100 %

Importance

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