-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathMask.php
More file actions
196 lines (170 loc) · 4.59 KB
/
Copy pathMask.php
File metadata and controls
196 lines (170 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?php
declare(strict_types=1);
namespace Reinder83\BinaryFlags;
use BackedEnum;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use JsonSerializable;
use Traversable;
/**
* Immutable enum-mask value object.
*
* @template TEnum of BackedEnum
* @implements IteratorAggregate<int, TEnum>
*/
final class Mask implements Countable, IteratorAggregate, JsonSerializable
{
/** @var class-string<TEnum> */
private string $enumClass;
/** @var array<int, TEnum> */
private array $flags;
/**
* @param class-string<TEnum> $enumClass
* @param array<int|string, TEnum> $flags
*/
private function __construct(string $enumClass, array $flags)
{
$normalized = [];
foreach ($flags as $flag) {
if (!$flag instanceof $enumClass) {
throw new InvalidArgumentException(sprintf('Expected enum of type %s.', $enumClass));
}
if (!is_int($flag->value)) {
throw new InvalidArgumentException('Only int-backed enums are supported.');
}
$normalized[$flag->value] = $flag;
}
ksort($normalized);
$this->enumClass = $enumClass;
$this->flags = array_values($normalized);
}
/**
* @template TFlag of BackedEnum
* @param class-string<TFlag> $enumClass
* @param TFlag ...$flags
* @return self<TFlag>
*/
public static function forEnum(string $enumClass, BackedEnum ...$flags): self
{
return new self($enumClass, $flags);
}
/**
* @return self<Flag>
*/
public static function fromFlags(Flag ...$flags): self
{
return new self(Flag::class, $flags);
}
/**
* @template TFlag of BackedEnum
* @param class-string<TFlag> $enumClass
* @return self<TFlag>
*/
public static function fromInt(int $mask, string $enumClass = Flag::class): self
{
if (!enum_exists($enumClass)) {
throw new InvalidArgumentException(sprintf('Enum class %s does not exist.', $enumClass));
}
/** @var array<int, TFlag> $cases */
$cases = $enumClass::cases();
/** @var array<int, TFlag> $flags */
$flags = [];
foreach ($cases as $flag) {
if (!is_int($flag->value)) {
throw new InvalidArgumentException('Only int-backed enums are supported.');
}
if (($mask & $flag->value) !== 0) {
$flags[] = $flag;
}
}
return new self($enumClass, $flags);
}
/**
* @return class-string<TEnum>
*/
public function enumClass(): string
{
return $this->enumClass;
}
/**
* @return array<int, TEnum>
*/
public function flags(): array
{
return $this->flags;
}
public function toInt(): int
{
return array_reduce(
$this->flags,
fn(int $mask, BackedEnum $flag): int => $mask | (int) $flag->value,
0,
);
}
/**
* @param TEnum $flag
*/
public function has(BackedEnum $flag): bool
{
if (!$flag instanceof $this->enumClass) {
return false;
}
return in_array($flag, $this->flags, true);
}
/**
* @param TEnum ...$flags
* @return self<TEnum>
*/
public function add(BackedEnum ...$flags): self
{
return new self($this->enumClass, [...$this->flags, ...$flags]);
}
/**
* @param TEnum ...$flags
* @return self<TEnum>
*/
public function remove(BackedEnum ...$flags): self
{
$removeMap = [];
foreach ($flags as $flag) {
if (!$flag instanceof $this->enumClass) {
continue;
}
$removeMap[(int) $flag->value] = true;
}
return new self(
$this->enumClass,
array_values(
array_filter(
$this->flags,
fn(BackedEnum $flag): bool => !isset($removeMap[(int) $flag->value]),
),
),
);
}
public function count(): int
{
return count($this->flags);
}
/**
* @return Traversable<int, TEnum>
*/
public function getIterator(): Traversable
{
yield from $this->flags;
}
/**
* @return array{mask:int, flags: array<int, string>}
*/
public function jsonSerialize(): array
{
return [
'mask' => $this->toInt(),
'flags' => array_map(
fn(BackedEnum $flag): string => $flag->name,
$this->flags,
),
];
}
}