Passed
Pull Request — master (#99)
by Def
02:50
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\Translator\TranslatorInterface;
8
use Yiisoft\Validator\DataSetInterface;
9
use Yiisoft\Validator\ErrorMessage;
10
use Yiisoft\Validator\Result;
11
use Yiisoft\Validator\Rule;
12
13
/**
14
 * CompareValidator compares the specified attribute value with another value.
15
 *
16
 * The value being compared with a constant [[compareValue]], which is set
17
 * in the constructor.
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
     *
31
     * @see type
32
     */
33
    private const TYPE_STRING = 'string';
34
    /**
35
     * Constant for specifying the comparison [[type]] by numeric values.
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. 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 7
    private function getMessage(): string
80
    {
81 7
        switch ($this->operator) {
82 7
            case '==':
83 3
            case '===':
84 5
                return 'Value must be equal to "{value}".';
85 3
            case '!=':
86 3
            case '!==':
87 2
                return 'Value must not be equal to "{value}".';
88 2
            case '>':
89 1
                return 'Value must be greater than "{value}".';
90 2
            case '>=':
91 2
                return 'Value must be greater than or equal to "{value}".';
92 1
            case '<':
93 1
                return 'Value must be less than "{value}".';
94 1
            case '<=':
95 1
                return 'Value must be less than or equal to "{value}".';
96
            default:
97
                throw new \RuntimeException("Unknown operator: {$this->operator}");
98
        }
99
    }
100
101 2
    public function __construct($value)
102
    {
103 2
        $this->compareValue = $value;
104 2
    }
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
        $new = clone $this;
113 1
        $new->operator = $operator;
114 1
        return $new;
115
    }
116
117
    public function asString(): self
118
    {
119
        $new = clone $this;
120
        $new->type = self::TYPE_STRING;
121
        return $new;
122
    }
123
124
    public function asNumber(): self
125
    {
126
        $new = clone $this;
127
        $new->type = self::TYPE_NUMBER;
128
        return $new;
129
    }
130
131 1
    protected function validateValue($value, DataSetInterface $dataSet = null): Result
132
    {
133 1
        $result = new Result();
134
135 1
        if ($this->compareValue === null) {
136
            throw new \RuntimeException('CompareValidator::compareValue must be set.');
137
        }
138
139 1
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
140 1
            $result->addError(
141 1
                $this->getMessage(),
142
                [
143 1
                    'value' => $this->compareValue,
144
                ]
145
            );
146
        }
147
148 1
        return $result;
149
    }
150
151
    /**
152
     * Compares two values with the specified operator.
153
     *
154
     * @param string $operator the comparison operator
155
     * @param string $type the type of the values being compared
156
     * @param mixed $value the value being compared
157
     * @param mixed $compareValue another value being compared
158
     *
159
     * @return bool whether the comparison using the specified operator is true.
160
     */
161 1
    protected function compareValues(string $operator, string $type, $value, $compareValue): bool
162
    {
163 1
        if ($type === self::TYPE_NUMBER) {
164
            $value = (float)$value;
165
            $compareValue = (float)$compareValue;
166
        } else {
167 1
            $value = (string)$value;
168 1
            $compareValue = (string)$compareValue;
169
        }
170 1
        switch ($operator) {
171 1
            case '==':
172 1
                return $value == $compareValue;
173 1
            case '===':
174 1
                return $value === $compareValue;
175 1
            case '!=':
176 1
                return $value != $compareValue;
177 1
            case '!==':
178 1
                return $value !== $compareValue;
179 1
            case '>':
180 1
                return $value > $compareValue;
181 1
            case '>=':
182 1
                return $value >= $compareValue;
183 1
            case '<':
184 1
                return $value < $compareValue;
185 1
            case '<=':
186 1
                return $value <= $compareValue;
187
            default:
188
                return false;
189
        }
190
    }
191
192 6
    public function getOptions(?TranslatorInterface $translator = null): array
193
    {
194 6
        return array_merge(
195 6
            parent::getOptions(),
196
            [
197 6
                'type' => $this->type,
198 6
                'operator' => $this->operator,
199 6
                'compareValue' => $this->compareValue,
200 6
                'message' => new ErrorMessage($this->getMessage(), ['value' => $this->compareValue], $translator),
201
            ],
202
        );
203
    }
204
}
205