Completed
Pull Request — master (#31)
by Maxime
02:00
created

FlaggedEnum::readables()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
crap 2.032
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
            return false;
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
    public static function readables(): array
46
    {
47 8
        $readables = [];
48
        $constants = (new \ReflectionClass(static::class))->getConstants();
49 8
        foreach (static::values() as $value) {
50
            $constantName = array_search($value, $constants, true);
51
            $readables[$value] = ucfirst(strtolower(str_replace('_', ' ', $constantName)));
52 8
        }
53 1
54
        return $readables;
55
    }
56 7
57
    /**
58 7
     * {@inheritdoc}
59 5
     *
60
     * @param string $separator A delimiter used between each bit flag's readable string
61
     */
62 2
    public static function readableFor($value, string $separator = '; '): string
63
    {
64 2
        if (!static::accepts($value)) {
65 2
            throw new InvalidValueException($value, static::class);
66 2
        }
67
        if ($value === self::NONE) {
68
            return static::readableForNone();
69
        }
70 2
71
        $humanRepresentations = static::readables();
72
73
        if (isset($humanRepresentations[$value])) {
74
            return $humanRepresentations[$value];
75
        }
76
77
        $parts = [];
78 1
79
        foreach ($humanRepresentations as $flag => $readableValue) {
80 1
            if ($flag === ($flag & $value)) {
81
                $parts[] = $readableValue;
82
            }
83
        }
84
85
        return implode($separator, $parts);
86
    }
87
88
    /**
89
     * Gets the human representation for the none value.
90 26
     *
91
     * @return string
92 26
     */
93
    protected static function readableForNone(): string
94 26
    {
95 1
        return 'None';
96 1
    }
97 1
98 1
    /**
99 1
     * Gets an integer value of the possible flags for enumeration.
100
     *
101 1
     * @throws LogicException If the possibles values are not valid bit flags
102
     *
103
     * @return int
104 1
     */
105
    private static function getBitmask(): int
106
    {
107
        $enumType = static::class;
108
109 25
        if (!isset(self::$masks[$enumType])) {
110
            $mask = 0;
111
            foreach (static::values() as $flag) {
112
                if ($flag < 1 || ($flag > 1 && ($flag % 2) !== 0)) {
113
                    throw new LogicException(sprintf(
114
                        'Possible value %s of the enumeration "%s" is not a bit flag.',
115
                        json_encode($flag),
116
                        static::class
117 7
                    ));
118
                }
119 7
                $mask |= $flag;
120
            }
121
            self::$masks[$enumType] = $mask;
122
        }
123
124
        return self::$masks[$enumType];
125
    }
126
127 13
    /**
128
     * {@inheritdoc}
129 13
     *
130 7
     * @param string $separator A delimiter used between each bit flag's readable string
131 7
     */
132 7
    public function getReadable(string $separator = '; '): string
133 7
    {
134
        return static::readableFor($this->getValue(), $separator);
135
    }
136
137
    /**
138 13
     * Gets an array of bit flags of the value.
139
     *
140
     * @return array
141
     */
142
    public function getFlags(): array
143
    {
144
        if ($this->flags === null) {
145
            $this->flags = [];
146
            foreach (static::values() as $flag) {
147
                if ($this->hasFlag($flag)) {
148 9
                    $this->flags[] = $flag;
149
                }
150 9
            }
151 9
        }
152
153
        return $this->flags;
154
    }
155
156
    /**
157
     * Determines whether the specified flag is set in a numeric value.
158
     *
159
     * @param int $bitFlag The bit flag or bit flags
160
     *
161
     * @return bool True if the bit flag or bit flags are also set in the current instance; otherwise, false
162
     */
163
    public function hasFlag(int $bitFlag): bool
164
    {
165
        if ($bitFlag >= 1) {
166 2
            return $bitFlag === ($bitFlag & $this->value);
167
        }
168 2
169 1
        return false;
170
    }
171
172 1
    /**
173
     * Computes a new value with given flags, and returns the corresponding instance.
174
     *
175
     * @param int $flags The bit flag or bit flags
176
     *
177
     * @throws InvalidValueException When $flags is not acceptable for this enumeration type
178
     *
179
     * @return static The enum instance for computed value
180
     */
181
    public function withFlags(int $flags): self
182
    {
183
        if (!static::accepts($flags)) {
184 4
            throw new InvalidValueException($flags, static::class);
185
        }
186 4
187 1
        return static::get($this->value | $flags);
188
    }
189
190 3
    /**
191
     * Computes a new value without given flags, and returns the corresponding instance.
192
     *
193
     * @param int $flags The bit flag or bit flags
194
     *
195
     * @throws InvalidValueException When $flags is not acceptable for this enumeration type
196
     *
197
     * @return static The enum instance for computed value
198
     */
199
    public function withoutFlags(int $flags): self
200
    {
201
        if (!static::accepts($flags)) {
202
            throw new InvalidValueException($flags, static::class);
203
        }
204
205
        return static::get($this->value & ~$flags);
206
    }
207
}
208