Test Failed
Pull Request — master (#219)
by
unknown
02:42
created

CompareTo::getMessage()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 23
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 10.0578

Importance

Changes 0
Metric Value
cc 10
eloc 19
c 0
b 0
f 0
nc 10
nop 0
dl 0
loc 23
ccs 11
cts 12
cp 0.9167
crap 10.0578
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Attribute;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Yiisoft\Validator\FormatterInterface;
11
use Yiisoft\Validator\Result;
12
use Yiisoft\Validator\Rule;
13
use Yiisoft\Validator\ValidationContext;
14
15
/**
16
 * Compares the specified value with another value.
17
 *
18
 * The value being compared with a constant {@see CompareTo::$compareValue}, which is set
19
 * in the constructor.
20
 *
21
 * It supports different comparison operators, specified
22
 * via the {@see CompareTo::$operator}.
23
 *
24
 * The default comparison function is based on string values, which means the values
25
 * are compared byte by byte. When comparing numbers, make sure to change {@see CompareTo::$type} to
26
 * {@see CompareTo::TYPE_NUMBER} to enable numeric comparison.
27
 */
28
#[Attribute(Attribute::TARGET_PROPERTY)]
29
final class CompareTo extends Rule
30
{
31
    /**
32
     * Constant for specifying the comparison as string values.
33
     * No conversion will be done before comparison.
34
     *
35
     * @see $type
36
     */
37
    public const TYPE_STRING = 'string';
38
    /**
39
     * Constant for specifying the comparison as numeric values.
40
     * String values will be converted into numbers before comparison.
41
     *
42
     * @see $type
43
     */
44
    public const TYPE_NUMBER = 'number';
45
    private array $validOperators = [
46
        '==' => 1,
47
        '===' => 1,
48
        '!=' => 1,
49
        '!==' => 1,
50
        '>' => 1,
51
        '>=' => 1,
52
        '<' => 1,
53
        '<=' => 1,
54
    ];
55
56 30
    public function __construct(
57
        /**
58
         * @var mixed the constant value to be compared with.
59
         */
60
        private mixed $compareValue,
61
        /**
62
         * @var string|null user-defined error message
63
         */
64
        private ?string $message = null,
65
        /**
66
         * @var string the type of the values being compared.
67
         */
68
        private string $type = self::TYPE_STRING,
69
        /**
70
         * @var string the operator for comparison. The following operators are supported:
71
         *
72
         * - `==`: check if two values are equal. The comparison is done is non-strict mode.
73
         * - `===`: check if two values are equal. The comparison is done is strict mode.
74
         * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
75
         * - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
76
         * - `>`: check if value being validated is greater than the value being compared with.
77
         * - `>=`: check if value being validated is greater than or equal to the value being compared with.
78
         * - `<`: check if value being validated is less than the value being compared with.
79
         * - `<=`: check if value being validated is less than or equal to the value being compared with.
80
         *
81
         * When you want to compare numbers, make sure to also chabge @see CompareTo::$type} to
82
         * {@see CompareTo::TYPE_NUMBER}.
83
         */
84
        private string $operator = '==',
85
        ?FormatterInterface $formatter = null,
86
        bool $skipOnEmpty = false,
87
        bool $skipOnError = false,
88
        $when = null
89
    ) {
90 30
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
91
        $this->checkOperator($operator);
92 30
        $this->checkType($this->type);
93
    }
94
95
    private function checkOperator($value): void
96
    {
97 21
        if (!isset($this->validOperators[$value])) {
98
            throw new InvalidArgumentException("Operator \"$value\" is not supported.");
99 21
        }
100 1
    }
101
102
    private function checkType($value): void
103 20
    {
104 20
        if ($value !== self::TYPE_NUMBER && $value !== self::TYPE_STRING) {
105 15
            throw new InvalidArgumentException("Type \"$value\" is not supported.");
106 6
        }
107 14
    }
108 11
109 7
    /**
110 7
     * @see $compareValue
111 2
     */
112 5
    public function compareValue(mixed $value): self
113 2
    {
114 3
        $new = clone $this;
115 2
        $new->compareValue = $value;
116 1
117 1
        return $new;
118
    }
119
120
    /**
121
     * @see $message
122
     */
123 29
    public function message(string $value): self
124
    {
125 29
        $new = clone $this;
126
        $new->message = $value;
127 29
128 14
        return $new;
129 14
    }
130
131
    /**
132 29
     * @see $type
133
     */
134
    public function asString(): self
135
    {
136
        $new = clone $this;
137
        $new->type = self::TYPE_STRING;
138
139
        return $new;
140
    }
141
142
    /**
143
     * @see $type
144
     */
145 29
    public function asNumber(): self
146
    {
147 29
        $new = clone $this;
148
        $new->type = self::TYPE_NUMBER;
149
150
        return $new;
151 29
    }
152 29
153
    /**
154 29
     * @see $operator
155 29
     */
156 4
    public function operator(string $value): self
157 25
    {
158 4
        $this->checkOperator($value);
159 21
160 5
        $new = clone $this;
161 16
        $new->operator = $value;
162 4
163 12
        return $new;
164 3
    }
165 9
166 3
    private function getMessage(): string
167 6
    {
168 3
        if ($this->message !== null) {
169 3
            return $this->message;
170 3
        }
171
172
        switch ($this->operator) {
173
            case '==':
174
            case '===':
175
                return 'Value must be equal to "{value}".';
176 7
            case '!=':
177
            case '!==':
178 7
                return 'Value must not be equal to "{value}".';
179 7
            case '>':
180 7
                return 'Value must be greater than "{value}".';
181 7
            case '>=':
182 7
                return 'Value must be greater than or equal to "{value}".';
183
            case '<':
184
                return 'Value must be less than "{value}".';
185
            case '<=':
186
                return 'Value must be less than or equal to "{value}".';
187
            default:
188
                throw new RuntimeException("Unknown operator: {$this->operator}");
189
        }
190
    }
191
192
    protected function validateValue($value, ?ValidationContext $context = null): Result
193
    {
194
        $result = new Result();
195
196
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
197
            $message = $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]);
198
            $result->addError($message);
199
        }
200
201
        return $result;
202
    }
203
204
    /**
205
     * Compares two values with the specified operator.
206
     *
207
     * @param string $operator the comparison operator
208
     * @param string $type the type of the values being compared
209
     * @param mixed $value the value being compared
210
     * @param mixed $compareValue another value being compared
211
     *
212
     * @return bool whether the comparison using the specified operator is true.
213
     */
214
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
215
    {
216
        if ($type === self::TYPE_NUMBER) {
217
            $value = (float) $value;
218
            $compareValue = (float)$compareValue;
219
        } else {
220
            $value = (string) $value;
221
            $compareValue = (string) $compareValue;
222
        }
223
        switch ($operator) {
224
            case '==':
225
                return $value == $compareValue;
226
            case '===':
227
                return $value === $compareValue;
228
            case '!=':
229
                return $value != $compareValue;
230
            case '!==':
231
                return $value !== $compareValue;
232
            case '>':
233
                return $value > $compareValue;
234
            case '>=':
235
                return $value >= $compareValue;
236
            case '<':
237
                return $value < $compareValue;
238
            case '<=':
239
                return $value <= $compareValue;
240
            default:
241
                return false;
242
        }
243
    }
244
245
    public function getOptions(): array
246
    {
247
        return array_merge(parent::getOptions(), [
248
            'compareValue' => $this->compareValue,
249
            'message' => $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]),
250
            'type' => $this->type,
251
            'operator' => $this->operator,
252
        ]);
253
    }
254
}
255