Completed
Push — master ( 062e20...a3fd36 )
by Maxime
01:41
created

FlaggedEnum::getBitmask()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.0208

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 11
cts 12
cp 0.9167
rs 8.7624
c 0
b 0
f 0
nc 3
cc 6
eloc 13
nop 0
crap 6.0208
1
<?php
2
3
/*
4
 * This file is part of the "elao/enum" package.
5
 *
6
 * Copyright (C) 2016 Elao
7
 *
8
 * @author Elao <[email protected]>
9
 */
10
11
namespace Elao\Enum;
12
13
use Elao\Enum\Exception\InvalidValueException;
14
use Elao\Enum\Exception\LogicException;
15
16
abstract class FlaggedEnum extends ReadableEnum
17
{
18
    const NONE = 0;
19
20
    /** @var array */
21
    private static $masks = [];
22
23
    /** @var int[] */
24
    protected $flags;
25
26
    /**
27
     * {@inheritdoc}
28
     */
29 30
    public static function accepts($value): bool
30
    {
31 30
        if (!is_int($value)) {
32 1
            throw new InvalidValueException($value, static::class);
33
        }
34
35 29
        if ($value === self::NONE) {
36 3
            return true;
37
        }
38
39 26
        return $value === ($value & self::getBitmask());
40
    }
41
42
    /**
43
     * {@inheritdoc}
44
     *
45
     * @param string $separator A delimiter used between each bit flag's readable string
46
     */
47 8
    public static function readableFor($value, string $separator = '; '): string
48
    {
49 8
        if (!static::accepts($value)) {
50
            throw new InvalidValueException($value, static::class);
51
        }
52 8
        if ($value === self::NONE) {
53 1
            return static::readableForNone();
54
        }
55
56 7
        $humanRepresentations = static::readables();
57
58 7
        if (isset($humanRepresentations[$value])) {
59 5
            return $humanRepresentations[$value];
60
        }
61
62 2
        $parts = [];
63
64 2
        foreach ($humanRepresentations as $flag => $readableValue) {
65 2
            if ($flag === ($flag & $value)) {
66 2
                $parts[] = $readableValue;
67
            }
68
        }
69
70 2
        return implode($separator, $parts);
71
    }
72
73
    /**
74
     * Gets the human representation for the none value.
75
     *
76
     * @return string
77
     */
78 1
    protected static function readableForNone(): string
79
    {
80 1
        return 'None';
81
    }
82
83
    /**
84
     * Gets an integer value of the possible flags for enumeration.
85
     *
86
     * @throws LogicException If the possibles values are not valid bit flags
87
     *
88
     * @return int
89
     */
90 26
    private static function getBitmask(): int
91
    {
92 26
        $enumType = static::class;
93
94 26
        if (!isset(self::$masks[$enumType])) {
95 1
            $mask = 0;
96 1
            foreach (static::values() as $flag) {
97 1
                if ($flag < 1 || ($flag > 1 && ($flag % 2) !== 0)) {
98 1
                    throw new LogicException(sprintf(
99 1
                        'Possible value %s of the enumeration "%s" is not a bit flag.',
100
                        json_encode($flag),
101 1
                        static::class
102
                    ));
103
                }
104 1
                $mask |= $flag;
105
            }
106
            self::$masks[$enumType] = $mask;
107
        }
108
109 25
        return self::$masks[$enumType];
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     *
115
     * @param string $separator A delimiter used between each bit flag's readable string
116
     */
117 7
    public function getReadable(string $separator = '; '): string
118
    {
119 7
        return static::readableFor($this->getValue(), $separator);
120
    }
121
122
    /**
123
     * Gets an array of bit flags of the value.
124
     *
125
     * @return array
126
     */
127 13
    public function getFlags(): array
128
    {
129 13
        if ($this->flags === null) {
130 7
            $this->flags = [];
131 7
            foreach (static::values() as $flag) {
132 7
                if ($this->hasFlag($flag)) {
133 7
                    $this->flags[] = $flag;
134
                }
135
            }
136
        }
137
138 13
        return $this->flags;
139
    }
140
141
    /**
142
     * Determines whether the specified flag is set in a numeric value.
143
     *
144
     * @param int $bitFlag The bit flag or bit flags
145
     *
146
     * @return bool True if the bit flag or bit flags are also set in the current instance; otherwise, false
147
     */
148 9
    public function hasFlag(int $bitFlag): bool
149
    {
150 9
        if ($bitFlag >= 1) {
151 9
            return $bitFlag === ($bitFlag & $this->value);
152
        }
153
154
        return false;
155
    }
156
157
    /**
158
     * Computes a new value with given flags, and returns the corresponding instance.
159
     *
160
     * @param int $flags The bit flag or bit flags
161
     *
162
     * @throws InvalidValueException When $flags is not acceptable for this enumeration type
163
     *
164
     * @return static The enum instance for computed value
165
     */
166 2
    public function withFlags(int $flags): self
167
    {
168 2
        if (!static::accepts($flags)) {
169 1
            throw new InvalidValueException($flags, static::class);
170
        }
171
172 1
        return static::get($this->value | $flags);
173
    }
174
175
    /**
176
     * Computes a new value without given flags, and returns the corresponding instance.
177
     *
178
     * @param int $flags The bit flag or bit flags
179
     *
180
     * @throws InvalidValueException When $flags is not acceptable for this enumeration type
181
     *
182
     * @return static The enum instance for computed value
183
     */
184 4
    public function removeFlags(int $flags): self
185
    {
186 4
        if (!static::accepts($flags)) {
187 1
            throw new InvalidValueException($flags, static::class);
188
        }
189
190 3
        return static::get($this->value & ~$flags);
191
    }
192
}
193