Passed
Push — master ( 882143...e0e0b6 )
by Alexander
07:33
created

CompareTo::getMessage()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 9.0164

Importance

Changes 0
Metric Value
cc 9
eloc 17
nc 9
nop 0
dl 0
loc 19
ccs 16
cts 17
cp 0.9412
crap 9.0164
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use Yiisoft\Validator\DataSetInterface;
8
use Yiisoft\Validator\Result;
9
use Yiisoft\Validator\Rule;
10
11
/**
12
 * CompareValidator compares the specified attribute value with another value.
13
 *
14
 * The value being compared with a constant [[compareValue]], which is set
15
 * in the constructor.
16
 *
17
 * CompareValidator supports different comparison operators, specified
18
 * via the [[operator]] property.
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 set the [[$type]]
22
 * to [[TYPE_NUMBER]] to enable numeric comparison.
23
 */
24
class CompareTo extends Rule
25
{
26
    /**
27
     * Constant for specifying the comparison [[type]] by numeric values.
28
     * @see type
29
     */
30
    private const TYPE_STRING = 'string';
31
    /**
32
     * Constant for specifying the comparison [[type]] by numeric values.
33
     * @see type
34
     */
35
    private const TYPE_NUMBER = 'number';
36
37
    /**
38
     * @var mixed the constant value to be compared with.
39
     */
40
    private $compareValue;
41
    /**
42
     * @var string the type of the values being compared. The follow types are supported:
43
     *
44
     * - [[TYPE_STRING|string]]: the values are being compared as strings. No conversion will be done before comparison.
45
     * - [[TYPE_NUMBER|number]]: the values are being compared as numbers. String values will be converted into numbers before comparison.
46
     */
47
    private string $type = self::TYPE_STRING;
48
    /**
49
     * @var string the operator for comparison. The following operators are supported:
50
     *
51
     * - `==`: check if two values are equal. The comparison is done is non-strict mode.
52
     * - `===`: check if two values are equal. The comparison is done is strict mode.
53
     * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
54
     * - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
55
     * - `>`: check if value being validated is greater than the value being compared with.
56
     * - `>=`: check if value being validated is greater than or equal to the value being compared with.
57
     * - `<`: check if value being validated is less than the value being compared with.
58
     * - `<=`: check if value being validated is less than or equal to the value being compared with.
59
     *
60
     * When you want to compare numbers, make sure to also set [[type]] to `number`.
61
     */
62
    private string $operator = '==';
63
64
    private array $validOperators = [
65
        '==' => 1,
66
        '===' => 1,
67
        '!=' => 1,
68
        '!==' => 1,
69
        '>' => 1,
70
        '>=' => 1,
71
        '<' => 1,
72
        '<=' => 1,
73
    ];
74
75 7
    private function getMessage(): string
76
    {
77 7
        switch ($this->operator) {
78 7
            case '==':
79 3
            case '===':
80 5
                return 'Value must be equal to "{value}".';
81 3
            case '!=':
82 3
            case '!==':
83 2
                return 'Value must not be equal to "{value}".';
84 2
            case '>':
85 1
                return 'Value must be greater than "{value}".';
86 2
            case '>=':
87 2
                return 'Value must be greater than or equal to "{value}".';
88 1
            case '<':
89 1
                return 'Value must be less than "{value}".';
90 1
            case '<=':
91 1
                return 'Value must be less than or equal to "{value}".';
92
            default:
93
                throw new \RuntimeException("Unknown operator: {$this->operator}");
94
        }
95
    }
96
97
98 2
    public function __construct($value)
99
    {
100 2
        $this->compareValue = $value;
101 2
    }
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 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
136 1
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
137 1
            $result->addError(
138 1
                $this->translateMessage(
139 1
                    $this->getMessage(),
140
                    [
141 1
                        'value' => $this->compareValue,
142
                    ]
143
                )
144
            );
145
        }
146
147 1
        return $result;
148
    }
149
150
    /**
151
     * Compares two values with the specified operator.
152
     * @param string $operator the comparison operator
153
     * @param string $type the type of the values being compared
154
     * @param mixed $value the value being compared
155
     * @param mixed $compareValue another value being compared
156
     * @return bool whether the comparison using the specified operator is true.
157
     */
158 1
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
159
    {
160 1
        if ($type === self::TYPE_NUMBER) {
161
            $value = (float)$value;
162
            $compareValue = (float)$compareValue;
163
        } else {
164 1
            $value = (string)$value;
165 1
            $compareValue = (string)$compareValue;
166
        }
167 1
        switch ($operator) {
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 1
            case '<=':
183 1
                return $value <= $compareValue;
184
            default:
185
                return false;
186
        }
187
    }
188
189 6
    public function getOptions(): array
190
    {
191 6
        return array_merge(
192
            parent::getOptions(),
193
            [
194 6
                'type' => $this->type,
195 6
                'operator' => $this->operator,
196 6
                'compareValue' => $this->compareValue,
197 6
                'message' => $this->translateMessage($this->getMessage(), ['value' => $this->compareValue])
198
            ],
199
        );
200
    }
201
}
202