Passed
Pull Request — master (#502)
by Sergei
02:32
created

Each::getRulesDumper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 6
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\RuleInterface;
19
use Yiisoft\Validator\Helper\RulesDumper;
20
use Yiisoft\Validator\RuleWithOptionsInterface;
21
use Yiisoft\Validator\SkipOnEmptyInterface;
22
use Yiisoft\Validator\SkipOnErrorInterface;
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 Number(min: 0, max: 255, integerOnly: true),
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()}).
56
 *
57 6
 * @see EachHandler Corresponding handler performing the actual validation.
58
 *
59
 * @psalm-import-type WhenType from WhenInterface
60 2
 */
61
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
62 2
final class Each implements
63
    RuleWithOptionsInterface,
64
    SkipOnErrorInterface,
65 1
    WhenInterface,
66
    SkipOnEmptyInterface,
67 1
    PropagateOptionsInterface,
68 1
    AfterInitAttributeEventInterface
69 1
{
70 1
    use SkipOnEmptyTrait;
71
    use SkipOnErrorTrait;
72 1
    use WhenTrait;
73 1
74
    /**
75 1
     * @var iterable A set of normalized rules that needs to be applied to each element of the validated iterable.
76 1
     * @psalm-var iterable<RuleInterface>
77
     */
78
    private iterable $rules;
79 1
80
    /**
81 1
     * @param callable|iterable|RuleInterface $rules A set of rules that needs to be applied to each element of the
82 1
     * validated iterable. They will be normalized using {@see RulesNormalizer}.
83
     * @param string $incorrectInputMessage Error message used when validation fails because the validated value is not
84
     * an iterable.
85
     *
86 1
     * You may use the following placeholders in the message:
87
     *
88
     * - `{attribute}`: the translated label of the attribute being validated.
89
     * - `{type}`: the type of the value being validated.
90
     * @param string $incorrectInputKeyMessage Error message used when validation fails because the validated iterable
91
     * contains invalid keys. Only integer and string keys are allowed.
92 18
     *
93
     * You may use the following placeholders in the message:
94 18
     *
95
     * - `{attribute}`: the translated label of the attribute being validated.
96
     * - `{type}`: the type of the iterable key being validated.
97 5
     * @param bool|callable|null $skipOnEmpty Whether to skip this `Each` rule with all defined {@see $rules} if the
98
     * validated value is empty / not passed. See {@see SkipOnEmptyInterface}.
99 5
     * @param bool $skipOnError Whether to skip this `Each` rule with all defined {@see $rules} if any of the previous
100
     * rules gave an error. See {@see SkipOnErrorInterface}.
101
     * @param Closure|null $when A callable to define a condition for applying this `Each` rule with all defined
102 3
     * {@see $rules}. See {@see WhenInterface}.
103
     * @psalm-param WhenType $when
104 3
     */
105
    public function __construct(
106
        /**
107 3
         * @param callable|iterable<callable|RuleInterface>|RuleInterface $rules
108
         */
109
        iterable|callable|RuleInterface $rules = [],
110
        private string $incorrectInputMessage = 'Value must be array or iterable.',
111
        private string $incorrectInputKeyMessage = 'Every iterable key must have an integer or a string type.',
112
        private mixed $skipOnEmpty = null,
113
        private bool $skipOnError = false,
114
        private Closure|null $when = null,
115
    ) {
116
        $this->rules = RulesNormalizer::normalizeList($rules);
117
    }
118 3
119
    public function getName(): string
120
    {
121
        return 'each';
122 3
    }
123
124
    public function propagateOptions(): void
125 3
    {
126 3
        $this->rules = PropagateOptionsHelper::propagate($this, $this->rules);
127 3
    }
128
129
    /**
130
     * Gets a set of normalized rules that needs to be applied to each element of the validated iterable.
131 18
     *
132
     * @return iterable<Closure|Closure[]|RuleInterface|RuleInterface[]> A set of rules.
133 18
     *
134
     * @see $rules
135
     */
136
    public function getRules(): iterable
137
    {
138
        return $this->rules;
139
    }
140
141
    /**
142
     * Gets error message used when validation fails because the validated value is not an iterable.
143
     *
144
     * @return string Error message / template.
145
     *
146
     * @see $incorrectInputMessage
147
     */
148
    public function getIncorrectInputMessage(): string
149
    {
150
        return $this->incorrectInputMessage;
151
    }
152
153
    /**
154
     * Error message used when validation fails because the validated iterable contains invalid keys.
155
     *
156
     * @return string Error message / template.
157
     *
158
     * @see $incorrectInputKeyMessage
159
     */
160
    public function getIncorrectInputKeyMessage(): string
161
    {
162
        return $this->incorrectInputKeyMessage;
163
    }
164
165
    #[ArrayShape([
166
        'incorrectInputMessage' => 'array',
167
        'incorrectInputKeyMessage' => 'array',
168
        'skipOnEmpty' => 'bool',
169
        'skipOnError' => 'bool',
170
        'rules' => 'array',
171
    ])]
172
    public function getOptions(): array
173
    {
174
        return [
175
            'incorrectInputMessage' => [
176
                'template' => $this->incorrectInputMessage,
177
                'parameters' => [],
178
            ],
179
            'incorrectInputKeyMessage' => [
180
                'template' => $this->incorrectInputKeyMessage,
181
                'parameters' => [],
182
            ],
183
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
184
            'skipOnError' => $this->skipOnError,
185
            'rules' => $this->dumpRulesAsArray(),
186
        ];
187
    }
188
189
    public function getHandler(): string
190
    {
191
        return EachHandler::class;
192
    }
193
194
    public function afterInitAttribute(object $object, int $target): void
195
    {
196
        foreach ($this->rules as $rule) {
197
            if ($rule instanceof AfterInitAttributeEventInterface) {
198
                $rule->afterInitAttribute(
199
                    $object,
200
                    $target === Attribute::TARGET_CLASS ? Attribute::TARGET_PROPERTY : $target
201
                );
202
            }
203
        }
204
    }
205
206
    /**
207
     * Dumps defined {@see $rules} to array.
208
     *
209
     * @return array The array of rules with their options.
210
     */
211
    private function dumpRulesAsArray(): array
212
    {
213
        return RulesDumper::asArray($this->getRules());
214
    }
215
}
216