Passed
Pull Request — master (#519)
by
unknown
03:22 queued 30s
created

AbstractCompare::getHandler()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
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
/**
18
 * Abstract base for all the comparison validation rules.
19
 *
20
 * The value being compared with {@see AbstractCompare::$targetValue} or {@see AbstractCompare::$targetAttribute}, which
21
 * is set in the constructor.
22
 *
23
 * It supports different comparison operators, specified
24
 * via the {@see AbstractCompare::$operator}.
25
 *
26
 * The default comparison is based on string values, which means the values
27
 * are compared byte by byte. When comparing numbers, make sure to change {@see AbstractCompare::$type} to
28
 * {@see AbstractCompare::TYPE_NUMBER} to enable numeric comparison.
29
 *
30
 * @see CompareHandler
31
 *
32
 * @psalm-import-type WhenType from WhenInterface
33
 */
34
abstract class AbstractCompare implements
35
    RuleWithOptionsInterface,
36
    SkipOnEmptyInterface,
37
    SkipOnErrorInterface,
38
    WhenInterface
39
{
40
    use SkipOnEmptyTrait;
41
    use SkipOnErrorTrait;
42
    use WhenTrait;
43
44
    /**
45
     * Constant for specifying the comparison as string values.
46
     * Values will be converted to strings before comparison.
47
     *
48
     * @see $type
49
     */
50
    public const TYPE_STRING = 'string';
51
    /**
52
     * Constant for specifying the comparison as numeric values.
53
     * Values will be converted to float numbers before comparison.
54
     *
55
     * @see $type
56
     */
57
    public const TYPE_NUMBER = 'number';
58
59
    /**
60
     * @var array Map of valid operators.
61
     * It's used instead of a list for better performance.
62
     */
63
    private array $validOperatorsMap = [
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 AbstractCompare::TYPE_STRING}
102
     * or {@see AbstractCompare::TYPE_NUMBER}.
103
     * @psalm-param AbstractCompare::TYPE_* $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 = 'The allowed types are integer, float, string, boolean and null.',
129
        private string $incorrectDataSetTypeMessage = 'The attribute value returned from a custom data set must have ' .
130
        'a scalar type.',
131
        private string|null $message = null,
132
        private string $type = self::TYPE_STRING,
133
        private string $operator = '==',
134
        private mixed $skipOnEmpty = null,
135
        private bool $skipOnError = false,
136
        private Closure|null $when = null,
137
    ) {
138
        if (!isset($this->validOperatorsMap[$this->operator])) {
139
            $wrapInQuotesCallable = static fn (string $operator): string => '"' . $operator . '"';
140
            /** @var string[] $validOperators */
141
            $validOperators = array_keys($this->validOperatorsMap);
142
            $validOperatorsString = implode(', ', array_map($wrapInQuotesCallable, $validOperators));
143
            $message = "Operator \"$operator\" is not supported. The valid operators are: $validOperatorsString.";
144
145
            throw new InvalidArgumentException($message);
146
        }
147
    }
148
149
    /**
150
     * Get the constant value to be compared with.
151
     *
152
     * @return scalar|null Value to be compared with or `null` if it was not configured.
153
     *
154
     * @see $targetValue
155
     */
156
    public function getTargetValue(): int|float|string|bool|null
157
    {
158
        return $this->targetValue;
159
    }
160
161
    /**
162
     * Get the name of the attribute to be compared with.
163
     *
164
     * @return string|null Name of the attribute to be compared with or `null` if it was not configured.
165
     *
166
     * @see $targetAttribute
167
     */
168
    public function getTargetAttribute(): string|null
169
    {
170
        return $this->targetAttribute;
171
    }
172
173
    /**
174
     * Get the type of the values being compared.
175
     *
176
     * @return string The type of the values being compared. Either {@see AbstractCompare::TYPE_STRING}
177
     * or {@see AbstractCompare::TYPE_NUMBER}.
178
     * @psalm-return AbstractCompare::TYPE_* $type
179
     *
180
     * @see $type
181
     */
182
    public function getType(): string
183
    {
184
        return $this->type;
185
    }
186
187
    /**
188
     * Get the operator for comparison.
189
     *
190
     * @return string The operator for comparison.
191
     *
192
     * @see $operator
193
     */
194
    public function getOperator(): string
195
    {
196
        return $this->operator;
197
    }
198
199
    /**
200
     * Get message used when the input is incorrect.
201
     *
202
     * @return string Error message.
203
     *
204
     * @see $incorrectInputMessage
205
     */
206
    public function getIncorrectInputMessage(): string
207
    {
208
        return $this->incorrectInputMessage;
209
    }
210
211
    /**
212
     * Get message used when the value returned from a custom
213
     * data set s not scalar.
214
     *
215
     * @return string Error message.
216
     *
217
     * @see $incorrectDataSetTypeMessage
218
     */
219
    public function getIncorrectDataSetTypeMessage(): string
220
    {
221
        return $this->incorrectDataSetTypeMessage;
222
    }
223
224
    /**
225
     * Get a message used when the value is not valid.
226
     *
227
     * @return string Error message.
228
     *
229
     * @see $message
230
     */
231
    public function getMessage(): string
232
    {
233
        return $this->message ?? match ($this->operator) {
234
            '==', '===' => 'Value must be equal to "{targetValueOrAttribute}".',
235
            '!=', '!==' => 'Value must not be equal to "{targetValueOrAttribute}".',
236
            '>' => 'Value must be greater than "{targetValueOrAttribute}".',
237
            '>=' => 'Value must be greater than or equal to "{targetValueOrAttribute}".',
238
            '<' => 'Value must be less than "{targetValueOrAttribute}".',
239
            '<=' => 'Value must be less than or equal to "{targetValueOrAttribute}".',
240
        };
241
    }
242
243
    public function getOptions(): array
244
    {
245
        $messageParameters = [
246
            'targetValue' => $this->targetValue,
247
            'targetAttribute' => $this->targetAttribute,
248
            'targetValueOrAttribute' => $this->targetValue ?? $this->targetAttribute,
249
        ];
250
251
        return [
252
            'targetValue' => $this->targetValue,
253
            'targetAttribute' => $this->targetAttribute,
254
            'incorrectInputMessage' => [
255
                'template' => $this->incorrectInputMessage,
256
                'parameters' => $messageParameters,
257
            ],
258
            'incorrectDataSetTypeMessage' => [
259
                'template' => $this->incorrectDataSetTypeMessage,
260
                'parameters' => $messageParameters,
261
            ],
262
            'message' => [
263
                'template' => $this->getMessage(),
264
                'parameters' => $messageParameters,
265
            ],
266
            'type' => $this->type,
267
            'operator' => $this->operator,
268
            'skipOnEmpty' => $this->getSkipOnEmptyOption(),
269
            'skipOnError' => $this->skipOnError,
270
        ];
271
    }
272
273
    public function getHandler(): string
274
    {
275
        return CompareHandler::class;
276
    }
277
}
278