Passed
Pull Request — master (#542)
by
unknown
02:31
created

AbstractCompare::getQuotedList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Closure;
8
use InvalidArgumentException;
9
use Yiisoft\Validator\Rule\Trait\SkipOnEmptyTrait;
10
use Yiisoft\Validator\Rule\Trait\SkipOnErrorTrait;
11
use Yiisoft\Validator\Rule\Trait\WhenTrait;
12
use Yiisoft\Validator\RuleWithOptionsInterface;
13
use Yiisoft\Validator\SkipOnEmptyInterface;
14
use Yiisoft\Validator\SkipOnErrorInterface;
15
use Yiisoft\Validator\WhenInterface;
16
17
use function in_array;
18
19
/**
20
 * Abstract base for all the comparison validation rules.
21
 *
22
 * The value being compared with {@see AbstractCompare::$targetValue} or {@see AbstractCompare::$targetAttribute}, which
23
 * is set in the constructor.
24
 *
25
 * It supports different comparison operators, specified
26
 * via the {@see AbstractCompare::$operator}.
27
 *
28
 * The default comparison is based on string values, which means the values
29
 * are compared byte by byte. When comparing numbers, make sure to change {@see AbstractCompare::$type} to
30
 * {@see CompareType::NUMBER} to enable numeric comparison.
31
 *
32
 * @see CompareHandler
33
 *
34
 * @psalm-import-type WhenType from WhenInterface
35
 */
36
abstract class AbstractCompare implements
37
    RuleWithOptionsInterface,
38
    SkipOnEmptyInterface,
39
    SkipOnErrorInterface,
40
    WhenInterface
41
{
42
    use SkipOnEmptyTrait;
43
    use SkipOnErrorTrait;
44
    use WhenTrait;
45
46
    /**
47
     * A default for {@see $incorrectInputMessage}.
48
     */
49
    protected const DEFAULT_INCORRECT_INPUT_MESSAGE = 'The allowed types are integer, float, string, boolean and null.';
50
    /**
51
     * A default for {@see $incorrectDataSetTypeMessage}.
52
     */
53
    protected const DEFAULT_INCORRECT_DATA_SET_TYPE_MESSAGE = 'The attribute value returned from a custom data set ' .
54
    'must have a scalar type.';
55
56
    /**
57
     * @var string[] List of valid types.
58
     * @see CompareType
59
     */
60
    private static array $validTypes = [CompareType::STRING, CompareType::NUMBER];
61
    /**
62
     * @var array Map of valid operators.
63
     * It's used instead of a list for better performance.
64
     * @psalm-var array<string, 1>
65
     */
66
    private static array $validOperatorsMap = [
67
        '==' => 1,
68
        '===' => 1,
69
        '!=' => 1,
70
        '!==' => 1,
71
        '>' => 1,
72
        '>=' => 1,
73
        '<' => 1,
74
        '<=' => 1,
75
    ];
76
77
    /**
78
     * @param scalar|null $targetValue The constant value to be compared with. When both this property and
79
     * {@see $targetAttribute} are set, this property takes precedence.
80
     * @param string|null $targetAttribute The name of the attribute to be compared with. When both this property and
81
     * {@see $targetValue} are set, the {@see $targetValue} takes precedence.
82
     * @param string $incorrectInputMessage A message used when the input is incorrect.
83
     *
84
     * You may use the following placeholders in the message:
85
     *
86
     * - `{attribute}`: the translated label of the attribute being validated.
87
     * - `{type}`: the type of the value being validated.
88
     * @param string $incorrectDataSetTypeMessage A message used when the value returned from a custom
89
     * data set is not scalar.
90
     *
91
     * You may use the following placeholders in the message:
92
     *
93
     * - `{type}`: type of the value.
94
     * @param string|null $message A message used when the value is not valid.
95
     *
96
     * You may use the following placeholders in the message:
97
     *
98
     * - `{attribute}`: the translated label of the attribute being validated.
99
     * - `{targetValue}`: the constant value to be compared with.
100
     * - `{targetAttribute}`: the name of the attribute to be compared with.
101
     * - `{targetValueOrAttribute}`: the constant value to be compared with or, if it's absent, the name of
102
     *   the attribute to be compared with.
103
     * - `{value}`: the value of the attribute being validated.
104
     * @param string $type The type of the values being compared. Either {@see CompareType::STRING}
105
     * or {@see CompareType::NUMBER}.
106
     * @psalm-param CompareType::STRING | CompareType::NUMBER $type
107
     *
108
     * @param string $operator The operator for comparison. The following operators are supported:
109
     *
110
     * - `==`: check if two values are equal. The comparison is done in non-strict mode.
111
     * - `===`: check if two values are equal. The comparison is done in strict mode.
112
     * - `!=`: check if two values are NOT equal. The comparison is done in non-strict mode.
113
     * - `!==`: check if two values are NOT equal. The comparison is done in strict mode.
114
     * - `>`: check if value being validated is greater than the value being compared with.
115
     * - `>=`: check if value being validated is greater than or equal to the value being compared with.
116
     * - `<`: check if value being validated is less than the value being compared with.
117
     * - `<=`: check if value being validated is less than or equal to the value being compared with.
118
     *
119
     * When you want to compare numbers, make sure to also change {@see $type} to {@see TYPE_NUMBER}.
120
     * @param bool|callable|null $skipOnEmpty Whether to skip this rule if the value validated is empty.
121
     * See {@see SkipOnEmptyInterface}.
122
     * @param bool $skipOnError Whether to skip this rule if any of the previous rules gave an error.
123
     * See {@see SkipOnErrorInterface}.
124
     * @param Closure|null $when A callable to define a condition for applying the rule.
125
     * See {@see WhenInterface}.
126
     * @psalm-param WhenType $when
127
     */
128
    public function __construct(
129
        private int|float|string|bool|null $targetValue = null,
130
        private string|null $targetAttribute = null,
131
        private string $incorrectInputMessage = self::DEFAULT_INCORRECT_INPUT_MESSAGE,
132
        private string $incorrectDataSetTypeMessage = self::DEFAULT_INCORRECT_DATA_SET_TYPE_MESSAGE,
133
        private string|null $message = null,
134
        private string $type = CompareType::STRING,
135
        private string $operator = '==',
136
        private mixed $skipOnEmpty = null,
137
        private bool $skipOnError = false,
138
        private Closure|null $when = null,
139
    ) {
140
        if (!in_array($this->type, self::$validTypes, true)) {
141
            $validTypesString = $this->getQuotedList(self::$validTypes);
142
            $message = "Type \"$this->type\" is not supported. The valid types are: $validTypesString.";
143
144
            throw new InvalidArgumentException($message);
145
        }
146
147
        if (!isset(self::$validOperatorsMap[$this->operator])) {
148
            $validOperators = array_keys(self::$validOperatorsMap);
149
            $validOperatorsString = $this->getQuotedList($validOperators);
150
            $message = "Operator \"$operator\" is not supported. The valid operators are: $validOperatorsString.";
151
152
            throw new InvalidArgumentException($message);
153
        }
154
    }
155
156
    /**
157
     * Get the constant value to be compared with.
158
     *
159
     * @return scalar|null Value to be compared with or `null` if it was not configured.
160
     *
161
     * @see $targetValue
162
     */
163
    public function getTargetValue(): int|float|string|bool|null
164
    {
165
        return $this->targetValue;
166
    }
167
168
    /**
169
     * Get the name of the attribute to be compared with.
170
     *
171
     * @return string|null Name of the attribute to be compared with or `null` if it was not configured.
172
     *
173
     * @see $targetAttribute
174
     */
175
    public function getTargetAttribute(): string|null
176
    {
177
        return $this->targetAttribute;
178
    }
179
180
    /**
181
     * Get the type of the values being compared.
182
     *
183
     * @return string The type of the values being compared. Either {@see CompareType::STRING}
184
     * or {@see CompareType::NUMBER}.
185
     * @psalm-return CompareType::STRING | CompareType::NUMBER $type
186
     *
187
     * @see $type
188
     */
189
    public function getType(): string
190
    {
191
        return $this->type;
192
    }
193
194
    /**
195
     * Get the operator for comparison.
196
     *
197
     * @return string The operator for comparison.
198
     *
199
     * @see $operator
200
     */
201
    public function getOperator(): string
202
    {
203
        return $this->operator;
204
    }
205
206
    /**
207
     * Get message used when the input is incorrect.
208
     *
209
     * @return string Error message.
210
     *
211
     * @see $incorrectInputMessage
212
     */
213
    public function getIncorrectInputMessage(): string
214
    {
215
        return $this->incorrectInputMessage;
216
    }
217
218
    /**
219
     * Get message used when the value returned from a custom
220
     * data set s not scalar.
221
     *
222
     * @return string Error message.
223
     *
224
     * @see $incorrectDataSetTypeMessage
225
     */
226
    public function getIncorrectDataSetTypeMessage(): string
227
    {
228
        return $this->incorrectDataSetTypeMessage;
229
    }
230
231
    /**
232
     * Get a message used when the value is not valid.
233
     *
234
     * @return string Error message.
235
     *
236
     * @see $message
237
     */
238
    public function getMessage(): string
239
    {
240
        return $this->message ?? match ($this->operator) {
241
            '==', '===' => 'Value must be equal to "{targetValueOrAttribute}".',
242
            '!=', '!==' => 'Value must not be equal to "{targetValueOrAttribute}".',
243
            '>' => 'Value must be greater than "{targetValueOrAttribute}".',
244
            '>=' => 'Value must be greater than or equal to "{targetValueOrAttribute}".',
245
            '<' => 'Value must be less than "{targetValueOrAttribute}".',
246
            '<=' => 'Value must be less than or equal to "{targetValueOrAttribute}".',
247
        };
248
    }
249
250
    public function getOptions(): array
251
    {
252
        $messageParameters = [
253
            'targetValue' => $this->targetValue,
254
            'targetAttribute' => $this->targetAttribute,
255
            'targetValueOrAttribute' => $this->targetValue ?? $this->targetAttribute,
256
        ];
257
258
        return [
259
            'targetValue' => $this->targetValue,
260
            'targetAttribute' => $this->targetAttribute,
261
            'incorrectInputMessage' => [
262
                'template' => $this->incorrectInputMessage,
263
                'parameters' => $messageParameters,
264
            ],
265
            'incorrectDataSetTypeMessage' => [
266
                'template' => $this->incorrectDataSetTypeMessage,
267
                'parameters' => $messageParameters,
268
            ],
269
            'message' => [
270
                'template' => $this->getMessage(),
271
                'parameters' => $messageParameters,
272
            ],
273
            'type' => $this->type,
274
            'operator' => $this->operator,
275
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
276
            'skipOnError' => $this->skipOnError,
277
        ];
278
    }
279
280
    public function getHandler(): string
281
    {
282
        return CompareHandler::class;
283
    }
284
285
    /**
286
     * Formats list of strings as a single string where items separated by the comma and each item is wrapped with
287
     * double quotes.
288
     *
289
     * For example, for `['item1', 'item2']` list, the output will be `"item1", "item2"`.
290
     *
291
     * @param string[] $items Initial list of strings to format.
292
     *
293
     * @return string Resulting formatted string.
294
     */
295
    private function getQuotedList(array $items): string
296
    {
297
        return '"'. implode('", "', $items) . '"';
0 ignored issues
show
Coding Style introduced by
Expected at least 1 space before "."; 0 found
Loading history...
298
    }
299
}
300