Set::subset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * GpsLab component.
5
 *
6
 * @author    Peter Gribanov <[email protected]>
7
 * @copyright Copyright (c) 2017, Peter Gribanov
8
 * @license   http://opensource.org/licenses/MIT
9
 */
10
11
namespace GpsLab\Component\Enum;
12
13
use GpsLab\Component\Enum\Exception\InvalidSetException;
14
use GpsLab\Component\Enum\Exception\OutOfEnumException;
15
16
class Set
17
{
18
    /**
19
     * @var int
20
     */
21
    private $bit = 0;
22
23
    /**
24
     * @var mixed[][]
25
     */
26
    private static $bits = [];
27
28
    /**
29
     * @var mixed[][]
30
     */
31
    private static $keys = [];
32
33
    /**
34
     * @param array $values
35
     */
36 23
    final public function __construct(array $values = [])
37
    {
38 23
        foreach ($values as $value) {
39 23
            $this->attach($value);
40
        }
41 23
    }
42
43
    /**
44
     * @return mixed[]
45
     */
46 5
    final public function values()
47
    {
48 5
        if (!$this->bit) {
49 1
            return [];
50
        }
51
52 5
        $values = [];
53 5
        foreach (self::$bits[get_called_class()] as $bit => $value) {
54 5
            if ($this->bit & $bit) {
55 5
                $values[] = $value;
56
            }
57
        }
58
59 5
        return $values;
60
    }
61
62
    /**
63
     * Attach the given value.
64
     *
65
     * @param mixed $value
66
     */
67 23
    final public function attach($value)
68
    {
69 23
        self::validateValue($value);
70
71 23
        $this->bit |= $this->bit($value);
72 23
    }
73
74
    /**
75
     * Detach the given value.
76
     *
77
     * @param mixed $value
78
     */
79 2
    final public function detach($value)
80
    {
81 2
        self::validateValue($value);
82
83 1
        $this->bit ^= $this->bit($value);
84 1
    }
85
86
    /**
87
     * Given value was attached.
88
     *
89
     * @param mixed $value
90
     *
91
     * @return bool
92
     */
93 2
    final public function contains($value)
94
    {
95 2
        self::validateValue($value);
96
97 1
        return (bool) ($this->bit & $this->bit($value));
98
    }
99
100
    /**
101
     * Check if this Set is the same as other.
102
     *
103
     * @param Set $set
104
     *
105
     * @return bool
106
     */
107 3
    final public function equal(self $set)
108
    {
109 3
        self::validateType($set);
110
111 2
        return $this->bit === $this->bit;
112
    }
113
114
    /**
115
     * Check if this Set is a subset of other.
116
     *
117
     * @param Set $set
118
     *
119
     * @return bool
120
     */
121 2
    final public function subset(self $set)
122
    {
123 2
        self::validateType($set);
124
125 1
        return ($this->bit & $set->bit) === $this->bit;
126
    }
127
128
    /**
129
     * Check if this Set is a superset of other.
130
     *
131
     * @param Set $set
132
     *
133
     * @return bool
134
     */
135 2
    final public function superset(self $set)
136
    {
137 2
        self::validateType($set);
138
139 1
        return ($this->bit | $set->bit) === $this->bit;
140
    }
141
142
    /**
143
     * Produce a new set with enum from both this and other (this | other).
144
     *
145
     * @param Set ...$set Other Set(s) of the same enum to produce the union
146
     *
147
     * @return Set
148
     */
149 2 View Code Duplication
    final public function union(self $set)
150
    {
151 2
        $bit = $this->bit;
152 2
        foreach (func_get_args() as $arg) {
153 2
            self::validateType($arg);
154
155 1
            $bit |= $arg->bit;
156
        }
157
158 1
        $clone = new static();
159 1
        $clone->bit = $bit;
160
161 1
        return $clone;
162
    }
163
164
    /**
165
     * Produce a new set with enum common to both this and other (this & other).
166
     *
167
     * @param Set ...$set Other Set(s) of the same enumeration to produce the union
168
     *
169
     * @return Set
170
     */
171 2 View Code Duplication
    final public function intersect(self $set)
172
    {
173 2
        $bit = $this->bit;
174 2
        foreach (func_get_args() as $arg) {
175 2
            self::validateType($arg);
176
177 1
            $bit &= $arg->bit;
178
        }
179
180 1
        $clone = new static();
181 1
        $clone->bit = $bit;
182
183 1
        return $clone;
184
    }
185
186
    /**
187
     * Produce a new set with enum in this but not in other (this - other).
188
     *
189
     * @param Set ...$set Other Set(s) of the same enumeration to produce the union
190
     *
191
     * @return Set
192
     */
193 2 View Code Duplication
    final public function diff(self $set)
194
    {
195 2
        $bit = 0;
196 2
        foreach (func_get_args() as $arg) {
197 2
            self::validateType($arg);
198
199 1
            $bit |= $arg->bit;
200
        }
201
202 1
        $clone = new static();
203 1
        $clone->bit = $this->bit & ~$bit;
204
205 1
        return $clone;
206
    }
207
208
    /**
209
     * Produce a new set with enum in either this and other but not in both (this ^ (other | other)).
210
     *
211
     * @param Set ...$set Other Set(s) of the same enumeration to produce the union
212
     *
213
     * @return Set
214
     */
215 2 View Code Duplication
    final public function symDiff(self $set)
216
    {
217 2
        $bit = 0;
218 2
        foreach (func_get_args() as $arg) {
219 2
            self::validateType($arg);
220
221 1
            $bit |= $arg->bit;
222
        }
223
224 1
        $clone = new static();
225 1
        $clone->bit = $this->bit ^ $bit;
226
227 1
        return $clone;
228
    }
229
230
    /**
231
     * Get choices for checkbox group.
232
     *
233
     * <code>
234
     * {
235
     *   value1: 'Readable value 1',
236
     *   value2: 'Readable value 2',
237
     * }
238
     * </code>
239
     *
240
     * @return array
241
     */
242 2
    final public static function choices()
243
    {
244 2
        $class = get_called_class();
245 2
        self::detectConstants($class);
246
247 2
        $choices = [];
248 2
        foreach (self::$keys[$class] as $value) {
249 2
            $choices[$value] = static::readable($value);
250
        }
251
252 2
        return $choices;
253
    }
254
255
    /**
256
     * @param mixed $value
257
     *
258
     * @return string
259
     */
260 3
    public static function readable($value)
261
    {
262 3
        self::validateValue($value);
263
264 3
        return array_search($value, self::$keys[get_called_class()]);
265
    }
266
267
    /**
268
     * @param mixed $value
269
     */
270 23
    private static function validateValue($value)
271
    {
272 23
        if (!in_array($value, self::bits(), true)) {
273 3
            throw OutOfEnumException::invalidValue($value, get_called_class());
274
        }
275 23
    }
276
277
    /**
278
     * @param object $object
279
     */
280 15
    private static function validateType($object)
281
    {
282 15
        $class = get_called_class();
283 15
        if ($class !== get_class($object)) {
284 7
            throw InvalidSetException::notInstanceOf($class, get_class($object));
285
        }
286 8
    }
287
288
    /**
289
     * @param string $class
290
     */
291 23
    private static function detectConstants($class)
292
    {
293 23
        if (!isset(self::$bits[$class])) {
294 2
            self::$keys[$class] = [];
295 2
            self::$bits[$class] = [];
296
297 2
            $bit = 1;
298 2
            foreach (ConstantDetector::detect($class) as $constant => $constant_value) {
299 2
                self::$keys[$class][$constant] = $constant_value;
300 2
                self::$bits[$class][$bit] = $constant_value;
301 2
                $bit += $bit;
302
            }
303
        }
304 23
    }
305
306
    /**
307
     * @param mixed $value
308
     *
309
     * @return int
310
     */
311 23
    private function bit($value)
312
    {
313 23
        return array_search($value, self::bits(), true);
314
    }
315
316
    /**
317
     * @return array
318
     */
319 23
    private static function bits()
320
    {
321 23
        $class = get_called_class();
322 23
        self::detectConstants($class);
323
324 23
        return self::$bits[$class];
325
    }
326
}
327