Passed
Pull Request — master (#99)
by Def
02:08
created

CompareTo::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

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