Passed
Pull Request — master (#25)
by Alexander
14:50
created

InRange::__construct()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 11
cc 4
rs 10
1
<?php
2
namespace Yiisoft\Validator\Rule;
3
4
use Yiisoft\Arrays\ArrayHelper;
5
use Yiisoft\Validator\DataSetInterface;
6
use Yiisoft\Validator\Result;
7
use Yiisoft\Validator\Rule;
8
9
/**
10
 * In validates that the attribute value is among a list of values.
11
 *
12
 * The range can be specified via the [[range]] property.
13
 * If the [[not]] property is set true, the validator will ensure the attribute value
14
 * is NOT among the specified range.
15
 *
16
 */
17
class InRange extends Rule
18
{
19
    /**
20
     * @var array|\Traversable|\Closure a list of valid values that the attribute value should be among or an anonymous function that returns
21
     * such a list. The signature of the anonymous function should be as follows,
22
     *
23
     * ```php
24
     * function($model, $attribute) {
25
     *     // compute range
26
     *     return $range;
27
     * }
28
     * ```
29
     */
30
    private $range;
31
    /**
32
     * @var bool whether the comparison is strict (both type and value must be the same)
33
     */
34
    private $strict = false;
35
    /**
36
     * @var bool whether to invert the validation logic. Defaults to false. If set to true,
37
     * the attribute value should NOT be among the list of values defined via [[range]].
38
     */
39
    private $not = false;
40
    /**
41
     * @var bool whether to allow array type attribute.
42
     */
43
    private $allowArray = false;
44
45
    private $message;
46
47
    public function __construct($range)
48
    {
49
        if (!is_array($range)
50
            && !($range instanceof \Closure)
51
            && !($range instanceof \Traversable)
52
        ) {
53
            throw new \RuntimeException('The "range" property must be set.');
54
        }
55
56
        $this->range = $range;
57
        $this->message = $this->formatMessage('{attribute} is invalid.');
58
    }
59
60
    public function strict(): self
61
    {
62
        $this->strict = true;
63
        return $this;
64
    }
65
66
    public function not(): self
67
    {
68
        $this->not = true;
69
        return $this;
70
    }
71
72
    public function message(string $message): self
73
    {
74
        $this->message = $message;
75
        return $this;
76
    }
77
78
    public function allowArray(bool $value): self
79
    {
80
        // TODO: do we really need this option?
81
        $this->allowArray = $value;
82
        return $this;
83
    }
84
85
    public function validateValue($value): Result
86
    {
87
        $in = false;
88
89
        if ($this->allowArray
90
            && ($value instanceof \Traversable || is_array($value))
91
            && static::isSubset($value, $this->range, $this->strict)
0 ignored issues
show
Bug introduced by
It seems like $this->range can also be of type Closure; however, parameter $haystack of Yiisoft\Validator\Rule\InRange::isSubset() does only seem to accept Traversable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

91
            && static::isSubset($value, /** @scrutinizer ignore-type */ $this->range, $this->strict)
Loading history...
92
        ) {
93
            $in = true;
94
        }
95
96
        if (!$in && static::isIn($value, $this->range, $this->strict)) {
0 ignored issues
show
Bug introduced by
It seems like $this->range can also be of type Closure; however, parameter $haystack of Yiisoft\Validator\Rule\InRange::isIn() does only seem to accept Traversable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
        if (!$in && static::isIn($value, /** @scrutinizer ignore-type */ $this->range, $this->strict)) {
Loading history...
97
            $in = true;
98
        }
99
100
        $result = new Result();
101
102
        if ($this->not === $in) {
103
            $result->addError($this->message);
104
        }
105
106
        return $result;
107
    }
108
109
    /**
110
     * Check whether an array or [[\Traversable]] contains an element.
111
     *
112
     * This method does the same as the PHP function [in_array()](https://secure.php.net/manual/en/function.in-array.php)
113
     * but additionally works for objects that implement the [[\Traversable]] interface.
114
     * @param mixed $needle The value to look for.
115
     * @param array|\Traversable $haystack The set of values to search.
116
     * @param bool $strict Whether to enable strict (`===`) comparison.
117
     * @return bool `true` if `$needle` was found in `$haystack`, `false` otherwise.
118
     * @throws InvalidArgumentException if `$haystack` is neither traversable nor an array.
119
     * @see https://secure.php.net/manual/en/function.in-array.php
120
     * @since 2.0.7
121
     */
122
    public static function isIn($needle, $haystack, $strict = false)
123
    {
124
        if ($haystack instanceof \Traversable) {
125
            foreach ($haystack as $value) {
126
                if ($needle == $value && (!$strict || $needle === $value)) {
127
                    return true;
128
                }
129
            }
130
        } elseif (is_array($haystack)) {
0 ignored issues
show
introduced by
The condition is_array($haystack) is always true.
Loading history...
131
            return in_array($needle, $haystack, $strict);
132
        } else {
133
            throw new InvalidArgumentException('Argument $haystack must be an array or implement Traversable');
0 ignored issues
show
Bug introduced by
The type Yiisoft\Validator\Rule\InvalidArgumentException was not found. Did you mean InvalidArgumentException? If so, make sure to prefix the type with \.
Loading history...
134
        }
135
136
        return false;
137
    }
138
139
    /**
140
     * Checks whether an array or [[\Traversable]] is a subset of another array or [[\Traversable]].
141
     *
142
     * This method will return `true`, if all elements of `$needles` are contained in
143
     * `$haystack`. If at least one element is missing, `false` will be returned.
144
     * @param array|\Traversable $needles The values that must **all** be in `$haystack`.
145
     * @param array|\Traversable $haystack The set of value to search.
146
     * @param bool $strict Whether to enable strict (`===`) comparison.
147
     * @throws InvalidArgumentException if `$haystack` or `$needles` is neither traversable nor an array.
148
     * @return bool `true` if `$needles` is a subset of `$haystack`, `false` otherwise.
149
     * @since 2.0.7
150
     */
151
    public static function isSubset($needles, $haystack, $strict = false)
152
    {
153
        if (is_array($needles) || $needles instanceof \Traversable) {
0 ignored issues
show
introduced by
$needles is always a sub-type of Traversable.
Loading history...
154
            foreach ($needles as $needle) {
155
                if (!static::isIn($needle, $haystack, $strict)) {
156
                    return false;
157
                }
158
            }
159
160
            return true;
161
        }
162
163
        throw new InvalidArgumentException('Argument $needles must be an array or implement Traversable');
164
    }
165
}
166