Passed
Push — master ( b2e03a...bc8242 )
by
unknown
04:12 queued 01:10
created

AbstractCompare::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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