Passed
Pull Request — master (#394)
by Sergei
29:24 queued 26:09
created

RulesNormalizer::prepareRulesArray()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 30
ccs 16
cts 16
cp 1
rs 8.4444
cc 8
nc 8
nop 3
crap 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Helper;
6
7
use InvalidArgumentException;
8
use ReflectionException;
9
use ReflectionProperty;
10
use Traversable;
11
use Yiisoft\Validator\Rule\Callback;
12
use Yiisoft\Validator\RuleInterface;
13
use Yiisoft\Validator\RulesProvider\AttributesRulesProvider;
14
use Yiisoft\Validator\RulesProviderInterface;
15
use Yiisoft\Validator\SkipOnEmptyInterface;
16
use Yiisoft\Validator\ValidatorInterface;
17
18
use function is_callable;
19
use function is_int;
20
use function is_object;
21
use function is_string;
22
23
/**
24
 * @psalm-import-type RulesType from ValidatorInterface
25
 */
26
final class RulesNormalizer
27
{
28
    /**
29
     * @psalm-param RulesType $rules
30
     *
31
     * @throws ReflectionException
32
     * @throws InvalidArgumentException
33
     *
34
     * @return iterable<int|string, iterable<int|string, RuleInterface>>
35
     */
36 831
    public static function normalize(
37
        iterable|object|string|null $rules,
38
        int $propertyVisibility = ReflectionProperty::IS_PRIVATE
39
        | ReflectionProperty::IS_PROTECTED
40
        | ReflectionProperty::IS_PUBLIC,
41
        mixed $data = null,
42
        ?callable $defaultSkipOnEmptyCriteria = null,
43
    ): iterable {
44 831
        $rules = self::prepareRulesArray($rules, $propertyVisibility, $data);
45
46 831
        $result = [];
47
48
        /**
49
         * @var mixed $attribute
50
         * @var mixed $attributeRules
51
         */
52 831
        foreach ($rules as $attribute => $attributeRules) {
53 819
            if (!is_int($attribute) && !is_string($attribute)) {
54 1
                throw new InvalidArgumentException(
55 1
                    sprintf(
56
                        'An attribute can only have an integer or a string type. %s given.',
57 1
                        get_debug_type($attribute),
58
                    )
59
                );
60
            }
61
62 818
            $result[$attribute] = self::normalizeAttributeRules(
63 818
                is_iterable($attributeRules) ? $attributeRules : [$attributeRules],
64
                $defaultSkipOnEmptyCriteria
65
            );
66
        }
67
68 830
        return $result;
69
    }
70
71
    /**
72
     * @psalm-param RulesType $rules
73
     *
74
     * @throws ReflectionException
75
     */
76 831
    private static function prepareRulesArray(
77
        iterable|object|string|null $rules,
78
        int $propertyVisibility,
79
        mixed $data,
80
    ): iterable {
81 831
        if ($rules === null) {
82 34
            return $data instanceof RulesProviderInterface
83 30
                ? $data->getRules()
84 34
                : [];
85
        }
86
87 801
        if ($rules instanceof RulesProviderInterface) {
88 2
            return $rules->getRules();
89
        }
90
91 799
        if ($rules instanceof RuleInterface) {
92 1
            return [$rules];
93
        }
94
95 798
        if (is_string($rules)) {
96 1
            return self::getRulesFromAttributes($rules, $propertyVisibility);
97
        }
98
99 797
        if (is_object($rules)) {
100 8
            return $rules instanceof Traversable
101 1
                ? $rules
102 8
                : self::getRulesFromAttributes($rules, $propertyVisibility);
103
        }
104
105 789
        return $rules;
106
    }
107
108
    /**
109
     * @param class-string|object $source
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|object at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|object.
Loading history...
110
     *
111
     * @throws ReflectionException
112
     */
113 8
    private static function getRulesFromAttributes(string|object $source, int $propertyVisibility): iterable
114
    {
115 8
        return (new AttributesRulesProvider($source, $propertyVisibility))->getRules();
116
    }
117
118
    /**
119
     * @throws InvalidArgumentException
120
     *
121
     * @return iterable<int|string, RuleInterface>
122
     */
123 818
    private static function normalizeAttributeRules(iterable $rules, ?callable $defaultSkipOnEmptyCriteria): iterable
124
    {
125
        /** @var mixed $rule */
126 818
        foreach ($rules as $rule) {
127 818
            yield self::normalizeRule($rule, $defaultSkipOnEmptyCriteria);
128
        }
129
    }
130
131
    /**
132
     * @throws InvalidArgumentException
133
     */
134 818
    private static function normalizeRule(mixed $rule, ?callable $defaultSkipOnEmptyCriteria): RuleInterface
135
    {
136 818
        if (is_callable($rule)) {
137 4
            return new Callback($rule);
138
        }
139
140 817
        if (!$rule instanceof RuleInterface) {
141 1
            throw new InvalidArgumentException(
142 1
                sprintf(
143
                    'Rule should be either an instance of %s or a callable, %s given.',
144
                    RuleInterface::class,
145 1
                    get_debug_type($rule)
146
                )
147
            );
148
        }
149
150 817
        if ($rule instanceof SkipOnEmptyInterface && $rule->getSkipOnEmpty() === null) {
151 729
            $rule = $rule->skipOnEmpty($defaultSkipOnEmptyCriteria);
152
        }
153
154 817
        return $rule;
155
    }
156
}
157