Passed
Pull Request — master (#566)
by Alexander
05:34 queued 02:40
created

Each::dumpRulesAsArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
cc 1
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Attribute;
8
use Closure;
9
use JetBrains\PhpStorm\ArrayShape;
10
use Yiisoft\Validator\AfterInitAttributeEventInterface;
11
use Yiisoft\Validator\DataSet\ObjectDataSet;
12
use Yiisoft\Validator\Helper\PropagateOptionsHelper;
13
use Yiisoft\Validator\Helper\RulesNormalizer;
14
use Yiisoft\Validator\PropagateOptionsInterface;
15
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
16
use Yiisoft\Validator\Rule\Trait\SkipOnErrorTrait;
17
use Yiisoft\Validator\Rule\Trait\WhenTrait;
18
use Yiisoft\Validator\Helper\RulesDumper;
19
use Yiisoft\Validator\RuleWithOptionsInterface;
20
use Yiisoft\Validator\SkipOnEmptyInterface;
21
use Yiisoft\Validator\SkipOnErrorInterface;
22
use Yiisoft\Validator\ValidatorInterface;
23
use Yiisoft\Validator\WhenInterface;
24
25
/**
26
 * Allows to define a set of rules for validating each element of an iterable.
27
 *
28
 * An example for simple iterable that can be used to validate RGB color:
29
 *
30
 * ```php
31
 * $rules = [
32
 *     new Count(exactly: 3), // Not required for using with `Each`.
33
 *     new Each([
34
 *         new Integer(min: 0, max: 255),
35
 *         // More rules can be added here.
36
 *     ]),
37
 * ];
38
 * ```
39 6
 *
40
 * When paired with {@see Nested} rule, it allows validation of related data:
41
 *
42
 * ```php
43
 * $coordinateRules = [new Number(min: -10, max: 10)];
44
 * $rule = new Each([
45
 *     new Nested([
46
 *         'coordinates.x' => $coordinateRules,
47
 *         'coordinates.y' => $coordinateRules,
48
 *     ]),
49
 * ]);
50
 * ```
51
 *
52
 * It's also possible to use DTO objects with PHP attributes, see {@see ObjectDataSet} documentation and guide for
53
 * details.
54
 *
55
 * Supports propagation of options (see {@see PropagateOptionsHelper::propagate()} for available options and
56
 * requirements).
57 6
 *
58
 * @see EachHandler Corresponding handler performing the actual validation.
59
 *
60 2
 * @psalm-import-type RawRules from ValidatorInterface
61
 * @psalm-import-type NormalizedRulesMap from RulesNormalizer
62 2
 * @psalm-import-type WhenType from WhenInterface
63
 */
64
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
65 1
final class Each implements
66
    RuleWithOptionsInterface,
67 1
    SkipOnEmptyInterface,
68 1
    SkipOnErrorInterface,
69 1
    WhenInterface,
70 1
    PropagateOptionsInterface,
71
    AfterInitAttributeEventInterface
72 1
{
73 1
    use SkipOnEmptyTrait;
74
    use SkipOnErrorTrait;
75 1
    use WhenTrait;
76 1
77
    /**
78
     * @var array Normalized rules to apply for each element of the validated iterable.
79 1
     * @psalm-var NormalizedRulesMap
80
     */
81 1
    private array $rules;
82 1
83
    /**
84
     * @param callable|iterable|object|string $rules Rules to apply for each element of the validated iterable.
85
     * They will be normalized using {@see RulesNormalizer}.
86 1
     * can be passed.
87
     * @psalm-param RawRules $rules
88
     *
89
     * @param string $incorrectInputMessage Error message used when validation fails because the validated value is not
90
     * an iterable.
91
     *
92 18
     * You may use the following placeholders in the message:
93
     *
94 18
     * - `{attribute}`: the translated label of the attribute being validated.
95
     * - `{type}`: the type of the value being validated.
96
     * @param string $incorrectInputKeyMessage Error message used when validation fails because the validated iterable
97 5
     * contains invalid keys. Only integer and string keys are allowed.
98
     *
99 5
     * You may use the following placeholders in the message:
100
     *
101
     * - `{attribute}`: the translated label of the attribute being validated.
102 3
     * - `{type}`: the type of the iterable key being validated.
103
     * @param bool|callable|null $skipOnEmpty Whether to skip this `Each` rule with all defined {@see $rules} if the
104 3
     * validated value is empty / not passed. See {@see SkipOnEmptyInterface}.
105
     * @param bool $skipOnError Whether to skip this `Each` rule with all defined {@see $rules} if any of the previous
106
     * rules gave an error. See {@see SkipOnErrorInterface}.
107 3
     * @param Closure|null $when A callable to define a condition for applying this `Each` rule with all defined
108
     * {@see $rules}. See {@see WhenInterface}.
109
     * @psalm-param WhenType $when
110
     */
111
    public function __construct(
112
        callable|iterable|object|string $rules = [],
113
        private string $incorrectInputMessage = 'Value must be array or iterable.',
114
        private string $incorrectInputKeyMessage = 'Every iterable key must have an integer or a string type.',
115
        private mixed $skipOnEmpty = null,
116
        private bool $skipOnError = false,
117
        private Closure|null $when = null,
118 3
    ) {
119
        $this->rules = RulesNormalizer::normalize($rules);
120
    }
121
122 3
    public function getName(): string
123
    {
124
        return 'each';
125 3
    }
126 3
127 3
    public function propagateOptions(): void
128
    {
129
        foreach ($this->rules as $key => $rules) {
130
            $this->rules[$key] = PropagateOptionsHelper::propagate($this, $rules);
131 18
        }
132
    }
133 18
134
    /**
135
     * Gets a set of rules that needs to be applied to each element of the validated iterable.
136
     *
137
     * @return array A set of rules.
138
     *
139
     * @psalm-return NormalizedRulesMap
140
     *
141
     * @see $rules
142
     */
143
    public function getRules(): array
144
    {
145
        return $this->rules;
146
    }
147
148
    /**
149
     * Gets error message used when validation fails because the validated value is not an iterable.
150
     *
151
     * @return string Error message / template.
152
     *
153
     * @see $incorrectInputMessage
154
     */
155
    public function getIncorrectInputMessage(): string
156
    {
157
        return $this->incorrectInputMessage;
158
    }
159
160
    /**
161
     * Error message used when validation fails because the validated iterable contains invalid keys.
162
     *
163
     * @return string Error message / template.
164
     *
165
     * @see $incorrectInputKeyMessage
166
     */
167
    public function getIncorrectInputKeyMessage(): string
168
    {
169
        return $this->incorrectInputKeyMessage;
170
    }
171
172
    #[ArrayShape([
173
        'incorrectInputMessage' => 'array',
174
        'incorrectInputKeyMessage' => 'array',
175
        'skipOnEmpty' => 'bool',
176
        'skipOnError' => 'bool',
177
        'rules' => 'array',
178
    ])]
179
    public function getOptions(): array
180
    {
181
        return [
182
            'incorrectInputMessage' => [
183
                'template' => $this->incorrectInputMessage,
184
                'parameters' => [],
185
            ],
186
            'incorrectInputKeyMessage' => [
187
                'template' => $this->incorrectInputKeyMessage,
188
                'parameters' => [],
189
            ],
190
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
191
            'skipOnError' => $this->skipOnError,
192
            'rules' => RulesDumper::asArray($this->rules),
193
        ];
194
    }
195
196
    public function getHandler(): string
197
    {
198
        return EachHandler::class;
199
    }
200
201
    public function afterInitAttribute(object $object, int $target): void
202
    {
203
        foreach ($this->rules as $attributeRules) {
204
            foreach ($attributeRules as $rule) {
205
                if ($rule instanceof AfterInitAttributeEventInterface) {
206
                    $rule->afterInitAttribute(
207
                        $object,
208
                        $target === Attribute::TARGET_CLASS ? Attribute::TARGET_PROPERTY : $target
209
                    );
210
                }
211
            }
212
        }
213
    }
214
}
215