Passed
Pull Request — master (#542)
by Alexander
04:58 queued 02:22
created

AbstractCompare::getOperator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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