Passed
Pull Request — master (#199)
by Alexander
02:50
created

CompareTo::compareValues()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 28
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10.1953

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 18
nop 4
dl 0
loc 28
ccs 21
cts 24
cp 0.875
crap 10.1953
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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