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