Passed
Pull Request — master (#219)
by
unknown
02:29
created

CompareTo::getMessage()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 23
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10.0145

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 10
nop 0
dl 0
loc 23
ccs 18
cts 19
cp 0.9474
crap 10.0145
rs 7.6666
c 0
b 0
f 0

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 37
    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 37
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
91 37
        $this->checkOperator($operator);
92 36
        $this->checkType($this->type);
93
    }
94
95 37
    private function checkOperator($value): void
96
    {
97 37
        if (!isset($this->validOperators[$value])) {
98 2
            throw new InvalidArgumentException("Operator \"$value\" is not supported.");
99
        }
100
    }
101
102 36
    private function checkType($value): void
103
    {
104 36
        if ($value !== self::TYPE_NUMBER && $value !== self::TYPE_STRING) {
105 1
            throw new InvalidArgumentException("Type \"$value\" is not supported.");
106
        }
107
    }
108
109
    /**
110
     * @see $compareValue
111
     */
112 1
    public function compareValue(mixed $value): self
113
    {
114 1
        $new = clone $this;
115 1
        $new->compareValue = $value;
116
117 1
        return $new;
118
    }
119
120
    /**
121
     * @see $type
122
     */
123 1
    public function asString(): self
124
    {
125 1
        $new = clone $this;
126 1
        $new->type = self::TYPE_STRING;
127
128 1
        return $new;
129
    }
130
131
    /**
132
     * @see $type
133
     */
134 1
    public function asNumber(): self
135
    {
136 1
        $new = clone $this;
137 1
        $new->type = self::TYPE_NUMBER;
138
139 1
        return $new;
140
    }
141
142
    /**
143
     * @see $operator
144
     */
145 2
    public function operator(string $value): self
146
    {
147 2
        $this->checkOperator($value);
148
149 1
        $new = clone $this;
150 1
        $new->operator = $value;
151
152 1
        return $new;
153
    }
154
155 25
    private function getMessage(): string
156
    {
157 25
        if ($this->message !== null) {
158 1
            return $this->message;
159
        }
160
161 24
        switch ($this->operator) {
162 24
            case '==':
163 16
            case '===':
164 10
                return 'Value must be equal to "{value}".';
165 14
            case '!=':
166 11
            case '!==':
167 7
                return 'Value must not be equal to "{value}".';
168 7
            case '>':
169 2
                return 'Value must be greater than "{value}".';
170 5
            case '>=':
171 2
                return 'Value must be greater than or equal to "{value}".';
172 3
            case '<':
173 2
                return 'Value must be less than "{value}".';
174 1
            case '<=':
175 1
                return 'Value must be less than or equal to "{value}".';
176
            default:
177
                throw new RuntimeException("Unknown operator: {$this->operator}");
178
        }
179
    }
180
181 29
    protected function validateValue($value, ?ValidationContext $context = null): Result
182
    {
183 29
        $result = new Result();
184
185 29
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
186 14
            $message = $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]);
187 14
            $result->addError($message);
188
        }
189
190 29
        return $result;
191
    }
192
193
    /**
194
     * Compares two values with the specified operator.
195
     *
196
     * @param string $operator the comparison operator
197
     * @param string $type the type of the values being compared
198
     * @param mixed $value the value being compared
199
     * @param mixed $compareValue another value being compared
200
     *
201
     * @return bool whether the comparison using the specified operator is true.
202
     */
203 29
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
204
    {
205 29
        if ($type === self::TYPE_NUMBER) {
206
            $value = (float) $value;
207
            $compareValue = (float)$compareValue;
208
        } else {
209 29
            $value = (string) $value;
210 29
            $compareValue = (string) $compareValue;
211
        }
212 29
        switch ($operator) {
213 29
            case '==':
214 4
                return $value == $compareValue;
215 25
            case '===':
216 4
                return $value === $compareValue;
217 21
            case '!=':
218 5
                return $value != $compareValue;
219 16
            case '!==':
220 4
                return $value !== $compareValue;
221 12
            case '>':
222 3
                return $value > $compareValue;
223 9
            case '>=':
224 3
                return $value >= $compareValue;
225 6
            case '<':
226 3
                return $value < $compareValue;
227 3
            case '<=':
228 3
                return $value <= $compareValue;
229
            default:
230
                return false;
231
        }
232
    }
233
234 11
    public function getOptions(): array
235
    {
236 11
        return array_merge(parent::getOptions(), [
237 11
            'compareValue' => $this->compareValue,
238 11
            'message' => $this->formatMessage($this->getMessage(), ['value' => $this->compareValue]),
239 11
            'type' => $this->type,
240 11
            'operator' => $this->operator,
241
        ]);
242
    }
243
}
244