Passed
Pull Request — master (#41)
by Alexander
09:27
created

CompareTo   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 154
Duplicated Lines 0 %

Test Coverage

Coverage 54.84%

Importance

Changes 0
Metric Value
eloc 73
dl 0
loc 154
ccs 34
cts 62
cp 0.5484
rs 10
c 0
b 0
f 0
wmc 27

7 Methods

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