Passed
Push — master ( 99ab46...a20a6f )
by Alexander
02:59
created

CompareTo::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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