Passed
Push — master ( 22adb7...6fe6d4 )
by Sergei
02:37
created

RulesNormalizer::prepareRulesIterable()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
dl 0
loc 23
rs 8.8333
c 0
b 0
f 0
eloc 10
nc 6
nop 2
ccs 0
cts 0
cp 0
crap 56
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Helper;
6
7
use InvalidArgumentException;
8
use ReflectionException;
9
use Yiisoft\Validator\RuleInterface;
10
use Yiisoft\Validator\RulesProvider\AttributesRulesProvider;
11
use Yiisoft\Validator\RulesProviderInterface;
12
use Yiisoft\Validator\Validator;
13
use Yiisoft\Validator\ValidatorInterface;
14
15
use function is_callable;
16
use function is_int;
17
use function is_string;
18
19
/**
20
 * A helper class used to normalize different types of data to the iterable with rule instances ({@see RuleInterface}).
21
 * Can be applied to the rules grouped by attributes with adding some default settings if needed.
22
 *
23
 * Note that when using {@see Validator}, normalization is performed automatically.
24
 *
25
 * @psalm-import-type RulesType from ValidatorInterface
26
 */
27
final class RulesNormalizer
28
{
29
    /**
30 824
     * Normalizes different types of data to the iterable with rule instances ({@see RuleInterface}) maintaining the
31
     * grouping by attributes and applying some default settings if needed.
32
     *
33
     * Based on rules source and additionally provided data this is what is done initially:
34
     *
35 824
     * - If rules' source is already an iterable, it will be left as is.
36
     * - If rules' source is not set (`null`) and validated data provided its own rules, they will be used instead.
37 823
     * - If rules' source is an object providing rules via separate method, they will be fetched and used.
38
     * - A single rule instance ({@see RuleInterface}) or a callable will be wrapped with array resulting in list with 1
39
     * item.
40
     * - If rules' source is a name of class providing rules via PHP attributes, they will be fetched and used.
41
     *
42
     * And then for every individual rule within a set by attribute:
43 823
     *
44 811
     * - A callable is wrapped with {@see Callback} rule.
45 1
     * - For any other type verifies that it's a valid rule instance.
46 1
     * - If default "skip on empty" criteria is set, applies it if possible.
47
     *
48 1
     * For attributes there is an additional internal validation for being integer / string.
49
     *
50
     * @param callable|iterable|object|string|null $rules Rules source. The following types are supported:
51
     *
52
     * - Iterable (can contain single rule instances and callables for individual attribute).
53 810
     * - `null`
54 810
     * - Object providing rules via separate method.
55
     * - Single rule instance / callable.
56
     * - Name of a class providing rules via PHP attributes.
57
     * @psalm-param RulesType $rules
58
     *
59 822
     * @param mixed|null $data Validated data,
60
     * @param callable|null $defaultSkipOnEmptyCriteria A default "skip on empty" criteria
61
     * ({@see SkipOnEmptyInterface}), already normalized. Used to optimize setting the same value in all the rules.
62
     * Defaults to `null` meaning that it's not used.
63
     *
64
     * @throws InvalidArgumentException When attribute is neither an integer nor a string.
65
     * @throws ReflectionException When parsing rules from PHP attributes failed.
66
     *
67 824
     * @return iterable Rules normalized as a whole and individually, ready to use for validation.
68
     * @psalm-return iterable<int|string, iterable<int, RuleInterface>>
69
     */
70
    public static function normalize(
71 824
        callable|iterable|object|string|null $rules,
72 34
        mixed $data = null,
73 30
        ?callable $defaultSkipOnEmptyCriteria = null,
74 34
    ): iterable {
75
        $rules = self::prepareRulesIterable($rules, $data);
76
77 794
        $normalizedRules = [];
78 2
79
        /**
80
         * @var mixed $attribute
81 792
         * @var mixed $attributeRules
82 1
         */
83
        foreach ($rules as $attribute => $attributeRules) {
84
            if (!is_int($attribute) && !is_string($attribute)) {
85
                throw new InvalidArgumentException(
86 791
                    sprintf(
87 790
                        'An attribute can only have an integer or a string type. %s given.',
88
                        get_debug_type($attribute),
89
                    )
90 1
                );
91
            }
92
93
            $normalizedRules[$attribute] = new RulesNormalizerIterator(
94
                is_iterable($attributeRules) ? $attributeRules : [$attributeRules],
95
                $defaultSkipOnEmptyCriteria,
96
            );
97
        }
98 810
99
        return $normalizedRules;
100
    }
101 810
102 810
    /**
103
     * Normalizes a set of rules using {@see RulesNormalizerIterator}. This is done for every individual rule:
104
     *
105
     * - Wrapping a callable with {@see Callback} rule.
106
     * - Verifying that it's a valid rule instance.
107
     *
108
     * @param callable|iterable|RuleInterface $rules A set of rules or a single rule for normalization.
109 810
     *
110
     * @throws InvalidArgumentException When at least one of the rules is neither a callable nor a {@see RuleInterface}
111 810
     * implementation.
112 4
     *
113
     * @return iterable An iterable with every rule checked and normalized.
114
     * @psalm-return iterable<int, RuleInterface>
115 809
     */
116 1
    public static function normalizeList(iterable|callable|RuleInterface $rules): iterable
117 1
    {
118
        return new RulesNormalizerIterator(
119
            is_iterable($rules) ? $rules : [$rules],
0 ignored issues
show
Bug introduced by
It seems like is_iterable($rules) ? $rules : array($rules) can also be of type Yiisoft\Validator\RuleInterface and callable; however, parameter $rules of Yiisoft\Validator\Helper...Iterator::__construct() does only seem to accept iterable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

119
            /** @scrutinizer ignore-type */ is_iterable($rules) ? $rules : [$rules],
Loading history...
120 1
        );
121
    }
122
123
    /**
124
     * Prepares rules' iterable based on provided rules' source and validated data:
125 809
     *
126 722
     * - If rules' source is already an iterable, it will be left as is.
127
     * - If rules' source is not set (`null`) and validated data provided its own rules, they will be used instead.
128
     * - If rules' source is an object providing rules via separate method, they will be fetched and used.
129 809
     * - A single rule instance ({@see RuleInterface}) or a callable will be wrapped with array resulting in list with 1
130
     * item.
131
     * - If rules' source is a name of class providing rules via PHP attributes, they will be fetched and used.
132
     *
133
     * Note that it only normalizes the type containing rules and not the rules themselves.
134
     *
135
     * @param callable|iterable|object|string|null $rules Rules source. The following types are supported:
136
     *
137
     * - Iterable.
138
     * - `null`
139
     * - Object providing rules via separate method.
140
     * - Single rule instance / callable.
141
     * - Name of a class providing rules via PHP attributes.
142
     * @psalm-param RulesType $rules
143
     *
144
     * @param mixed $data Validated data.
145
     *
146
     * @throws ReflectionException When parsing rules from PHP attributes failed.
147
     *
148
     * @return iterable An iterable with rules for further individual rules' normalization.
149
     */
150
    private static function prepareRulesIterable(
151
        callable|iterable|object|string|null $rules,
152
        mixed $data,
153
    ): iterable {
154
        if (is_iterable($rules)) {
155
            return $rules;
156
        }
157
158
        if ($rules === null) {
159
            return $data instanceof RulesProviderInterface
160
                ? $data->getRules()
161
                : [];
162
        }
163
164
        if ($rules instanceof RulesProviderInterface) {
165
            return $rules->getRules();
166
        }
167
168
        if ($rules instanceof RuleInterface || is_callable($rules)) {
169
            return [$rules];
170
        }
171
172
        return (new AttributesRulesProvider($rules))->getRules();
0 ignored issues
show
Bug introduced by
It seems like $rules can also be of type callable and iterable; however, parameter $source of Yiisoft\Validator\RulesP...Provider::__construct() does only seem to accept object|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
        return (new AttributesRulesProvider(/** @scrutinizer ignore-type */ $rules))->getRules();
Loading history...
173
    }
174
}
175