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

FlaggedEnum::readables()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

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