Passed
Pull Request — master (#542)
by
unknown
02:34
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
     * List of valid types.
57
     * @see CompareType
58
     */
59
    private const VALID_TYPES = [CompareType::STRING, CompareType::NUMBER];
60
    /**
61
     * Map of valid operators. It's used instead of a list for better performance.
62
     */
63
    private const VALID_OPERATORS_MAP = [
64
        '==' => 1,
65
        '===' => 1,
66
        '!=' => 1,
67
        '!==' => 1,
68
        '>' => 1,
69
        '>=' => 1,
70
        '<' => 1,
71
        '<=' => 1,
72
    ];
73
74
    /**
75
     * @param scalar|null $targetValue The constant value to be compared with. When both this property and
76
     * {@see $targetAttribute} are set, this property takes precedence.
77
     * @param string|null $targetAttribute The name of the attribute to be compared with. When both this property and
78
     * {@see $targetValue} are set, the {@see $targetValue} takes precedence.
79
     * @param string $incorrectInputMessage A message used when the input is incorrect.
80
     *
81
     * You may use the following placeholders in the message:
82
     *
83
     * - `{attribute}`: the translated label of the attribute being validated.
84
     * - `{type}`: the type of the value being validated.
85
     * @param string $incorrectDataSetTypeMessage A message used when the value returned from a custom
86
     * data set is not scalar.
87
     *
88
     * You may use the following placeholders in the message:
89
     *
90
     * - `{type}`: type of the value.
91
     * @param string|null $message A message used when the value is not valid.
92
     *
93
     * You may use the following placeholders in the message:
94
     *
95
     * - `{attribute}`: the translated label of the attribute being validated.
96
     * - `{targetValue}`: the constant value to be compared with.
97
     * - `{targetAttribute}`: the name of the attribute to be compared with.
98
     * - `{targetValueOrAttribute}`: the constant value to be compared with or, if it's absent, the name of
99
     *   the attribute to be compared with.
100
     * - `{value}`: the value of the attribute being validated.
101
     * @param string $type The type of the values being compared. Either {@see CompareType::STRING}
102
     * or {@see CompareType::NUMBER}.
103
     * @psalm-param CompareType::STRING | CompareType::NUMBER $type
104
     *
105
     * @param string $operator The operator for comparison. The following operators are supported:
106
     *
107
     * - `==`: check if two values are equal. The comparison is done in non-strict mode.
108
     * - `===`: check if two values are equal. The comparison is done in strict mode.
109
     * - `!=`: check if two values are NOT equal. The comparison is done in non-strict mode.
110
     * - `!==`: check if two values are NOT equal. The comparison is done in strict mode.
111
     * - `>`: check if value being validated is greater than the value being compared with.
112
     * - `>=`: check if value being validated is greater than or equal to the value being compared with.
113
     * - `<`: check if value being validated is less than the value being compared with.
114
     * - `<=`: check if value being validated is less than or equal to the value being compared with.
115
     *
116
     * When you want to compare numbers, make sure to also change {@see $type} to {@see TYPE_NUMBER}.
117
     * @param bool|callable|null $skipOnEmpty Whether to skip this rule if the value validated is empty.
118
     * See {@see SkipOnEmptyInterface}.
119
     * @param bool $skipOnError Whether to skip this rule if any of the previous rules gave an error.
120
     * See {@see SkipOnErrorInterface}.
121
     * @param Closure|null $when A callable to define a condition for applying the rule.
122
     * See {@see WhenInterface}.
123
     * @psalm-param WhenType $when
124
     */
125
    public function __construct(
126
        private int|float|string|bool|null $targetValue = null,
127
        private string|null $targetAttribute = null,
128
        private string $incorrectInputMessage = self::DEFAULT_INCORRECT_INPUT_MESSAGE,
129
        private string $incorrectDataSetTypeMessage = self::DEFAULT_INCORRECT_DATA_SET_TYPE_MESSAGE,
130
        private string|null $message = null,
131
        private string $type = CompareType::STRING,
132
        private string $operator = '==',
133
        private mixed $skipOnEmpty = null,
134
        private bool $skipOnError = false,
135
        private Closure|null $when = null,
136
    ) {
137
        if (!in_array($this->type, self::VALID_TYPES)) {
138
            $validTypesString = $this->getQuotedList(self::VALID_TYPES);
139
            $message = "Type \"$this->type\" is not supported. The valid types are: $validTypesString.";
140
141
            throw new InvalidArgumentException($message);
142
        }
143
144
        if (!isset(self::VALID_OPERATORS_MAP[$this->operator])) {
145
            $validOperators = array_keys(self::VALID_OPERATORS_MAP);
146
            $validOperatorsString = $this->getQuotedList($validOperators);
147
            $message = "Operator \"$operator\" is not supported. The valid operators are: $validOperatorsString.";
148
149
            throw new InvalidArgumentException($message);
150
        }
151
    }
152
153
    /**
154
     * Get the constant value to be compared with.
155
     *
156
     * @return scalar|null Value to be compared with or `null` if it was not configured.
157
     *
158
     * @see $targetValue
159
     */
160
    public function getTargetValue(): int|float|string|bool|null
161
    {
162
        return $this->targetValue;
163
    }
164
165
    /**
166
     * Get the name of the attribute to be compared with.
167
     *
168
     * @return string|null Name of the attribute to be compared with or `null` if it was not configured.
169
     *
170
     * @see $targetAttribute
171
     */
172
    public function getTargetAttribute(): string|null
173
    {
174
        return $this->targetAttribute;
175
    }
176
177
    /**
178
     * Get the type of the values being compared.
179
     *
180
     * @return string The type of the values being compared. Either {@see CompareType::STRING}
181
     * or {@see CompareType::NUMBER}.
182
     * @psalm-return CompareType::STRING | CompareType::NUMBER $type
183
     *
184
     * @see $type
185
     */
186
    public function getType(): string
187
    {
188
        return $this->type;
189
    }
190
191
    /**
192
     * Get the operator for comparison.
193
     *
194
     * @return string The operator for comparison.
195
     *
196
     * @see $operator
197
     */
198
    public function getOperator(): string
199
    {
200
        return $this->operator;
201
    }
202
203
    /**
204
     * Get message used when the input is incorrect.
205
     *
206
     * @return string Error message.
207
     *
208
     * @see $incorrectInputMessage
209
     */
210
    public function getIncorrectInputMessage(): string
211
    {
212
        return $this->incorrectInputMessage;
213
    }
214
215
    /**
216
     * Get message used when the value returned from a custom
217
     * data set s not scalar.
218
     *
219
     * @return string Error message.
220
     *
221
     * @see $incorrectDataSetTypeMessage
222
     */
223
    public function getIncorrectDataSetTypeMessage(): string
224
    {
225
        return $this->incorrectDataSetTypeMessage;
226
    }
227
228
    /**
229
     * Get a message used when the value is not valid.
230
     *
231
     * @return string Error message.
232
     *
233
     * @see $message
234
     */
235
    public function getMessage(): string
236
    {
237
        return $this->message ?? match ($this->operator) {
238
            '==', '===' => 'Value must be equal to "{targetValueOrAttribute}".',
239
            '!=', '!==' => 'Value must not be equal to "{targetValueOrAttribute}".',
240
            '>' => 'Value must be greater than "{targetValueOrAttribute}".',
241
            '>=' => 'Value must be greater than or equal to "{targetValueOrAttribute}".',
242
            '<' => 'Value must be less than "{targetValueOrAttribute}".',
243
            '<=' => 'Value must be less than or equal to "{targetValueOrAttribute}".',
244
        };
245
    }
246
247
    public function getOptions(): array
248
    {
249
        $messageParameters = [
250
            'targetValue' => $this->targetValue,
251
            'targetAttribute' => $this->targetAttribute,
252
            'targetValueOrAttribute' => $this->targetValue ?? $this->targetAttribute,
253
        ];
254
255
        return [
256
            'targetValue' => $this->targetValue,
257
            'targetAttribute' => $this->targetAttribute,
258
            'incorrectInputMessage' => [
259
                'template' => $this->incorrectInputMessage,
260
                'parameters' => $messageParameters,
261
            ],
262
            'incorrectDataSetTypeMessage' => [
263
                'template' => $this->incorrectDataSetTypeMessage,
264
                'parameters' => $messageParameters,
265
            ],
266
            'message' => [
267
                'template' => $this->getMessage(),
268
                'parameters' => $messageParameters,
269
            ],
270
            'type' => $this->type,
271
            'operator' => $this->operator,
272
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
273
            'skipOnError' => $this->skipOnError,
274
        ];
275
    }
276
277
    public function getHandler(): string
278
    {
279
        return CompareHandler::class;
280
    }
281
282
    /**
283
     * Formats list of strings as a single string where items separated by the comma and each item is wrapped with
284
     * double quotes.
285
     *
286
     * For example, for `['item1', 'item2']` list, the output will be `"item1", "item2"`.
287
     *
288
     * @param string[] $items Initial list of strings to format.
289
     *
290
     * @return string Resulting formatted string.
291
     */
292
    private function getQuotedList(array $items): string
293
    {
294
        return '"'. implode('", "', $items) . '"';
0 ignored issues
show
Coding Style introduced by
Expected at least 1 space before "."; 0 found
Loading history...
295
    }
296
}
297