Passed
Push — master ( 5f41a8...e9bec5 )
by Alexander
03:24 queued 01:16
created

CompareTo   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 172
Duplicated Lines 0 %

Test Coverage

Coverage 82.19%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 82
dl 0
loc 172
ccs 60
cts 73
cp 0.8219
rs 10
c 1
b 0
f 0
wmc 27

8 Methods

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