-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAbstractFlagable.php
More file actions
351 lines (313 loc) · 9.09 KB
/
AbstractFlagable.php
File metadata and controls
351 lines (313 loc) · 9.09 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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
<?php declare( strict_types = 1 );
namespace CodeKandis\Phlags;
use CodeKandis\Phlags\Exceptions\UnsupportedOperationException;
use CodeKandis\Phlags\Validation\FlagableValidator;
use CodeKandis\Phlags\Validation\InvalidFlagableException;
use CodeKandis\Phlags\Validation\InvalidValueException;
use CodeKandis\Phlags\Validation\ValueValidator;
use CodeKandis\Phlags\Validation\ValueValidatorInterface;
use ReflectionClass;
use Traversable;
use function explode;
use function implode;
use function is_int;
use function is_string;
/**
* Represents the base class of all flagable classes.
* @package codekandis/phlags
* @author Christian Ramelow <info@codekandis.net>
*/
abstract class AbstractFlagable implements FlagableInterface
{
/**
* Stores the states of all instantiated flagables.
* @var FlagableStateInterface[]
*/
protected static array $flagableStates = [];
/**
* Stores the current value of the flagable.
* @var int
*/
private int $value = 0;
/**
* Constructor method.
* @param int|string|FlagableInterface $value The initial value of the flagable.
* @throws InvalidFlagableException The flagable is invalid.
* @throws InvalidValueException The value is invalid.
*/
final public function __construct( $value = self::NONE )
{
static::initializeReflectedFlags();
static::validateFlagable();
static::getFlagableState()->setValueValidator( new ValueValidator() );
$this->set( $value );
}
/**
* Gets the state of the flagble.
* @return FlagableStateInterface The state of the flagable.
*/
private static function getFlagableState(): FlagableStateInterface
{
return static::$flagableStates[ static::class ] ?? static::$flagableStates[ static::class ] = new FlagableState();
}
/**
* Unsets an undefined member.
* @param string $memberName The name of the undefined member.
* @throws UnsupportedOperationException Accessing undefined members is not supported.
*/
final public function __unset( string $memberName ): void
{
throw new UnsupportedOperationException( 'Accessing undefined members is not supported.' );
}
/**
* Gets an undefined member.
* @param string $memberName The name of the undefined member.
* @return mixed The value of the undefined member.
* @throws UnsupportedOperationException Accessing undefined members is not supported.
*/
final public function __get( string $memberName )
{
throw new UnsupportedOperationException( 'Accessing undefined members is not supported.' );
}
/**
* Sets an undefined member.
* @param string $memberName The name of the undefined member.
* @param mixed $value The value to set.
* @throws UnsupportedOperationException Accessing undefined members is not supported.
*/
final public function __set( string $memberName, $value ): void
{
throw new UnsupportedOperationException( 'Accessing undefined members is not supported.' );
}
/**
* Calls an undefined method.
* @param string $methodName The name of the undefined method.
* @param array $arguments The passed arguments.
* @return mixed The return value of the undefined method.
* @throws UnsupportedOperationException Accessing undefined methods is not supported.
*/
final public function __call( string $methodName, array $arguments )
{
throw new UnsupportedOperationException( 'Accessing undefined methods is not supported.' );
}
/**
* Calls an undefined static method.
* @param string $methodName The name of the undefined static method.
* @param array $arguments The passed arguments.
* @return mixed The return value of the undefined static method.
* @throws UnsupportedOperationException Accessing undefined methods is not supported.
*/
final public static function __callStatic( string $methodName, array $arguments )
{
throw new UnsupportedOperationException( 'Accessing undefined methods is not supported.' );
}
/**
* {@inheritdoc}
*/
final public function __toString(): string
{
$flagsSetNames = [];
/**
* @var string $reflectedFlagName
* @var int $reflectedFlagValue
*/
foreach ( static::getFlagableState()->getReflectedFlags() as $reflectedFlagName => $reflectedFlagValue )
{
if ( 0 !== $reflectedFlagValue && ( $this->value & $reflectedFlagValue ) === $reflectedFlagValue )
{
$flagsSetNames[] = $reflectedFlagName;
}
}
return true === empty( $flagsSetNames )
? 'NONE'
: implode( '|', $flagsSetNames );
}
/**
* {@inheritdoc}
*/
final public function __invoke()
{
return $this->getValue();
}
/**
* {@inheritdoc}
*/
final public function getValue(): int
{
return $this->value;
}
/**
* Initializes the reflected flags for validation and stringification.
*/
private static function initializeReflectedFlags(): void
{
static::getFlagableState()->setReflectedFlags(
( new ReflectionClass( static::class ) )
->getConstants()
);
}
/**
* Validates the flagable.
* @throws InvalidFlagableException The flagable is invalid.
*/
private static function validateFlagable(): void
{
$flagableState = static::getFlagableState();
if ( true === $flagableState->getHasBeenValidated() && null !== $flagableState->getValidationException() )
{
throw $flagableState->getValidationException();
}
$flagableState->setHasBeenValidated( true );
$validator = new FlagableValidator();
$validator->validate( static::class, $flagableState->getReflectedFlags() );
if ( false === $validator->succeeded() )
{
/**
* @var InvalidFlagableException $validationException
*/
$validationException = ( new InvalidFlagableException( 'Invalid flagable.' ) )
->withErrorMessages( $validator->getErrorMessages() );
$flagableState->setValidationException( $validationException );
throw $validationException;
}
$flagableState->setMaxValue( $validator->getMaxValue() );
}
/**
* Validates a value.
* @param int|string|FlagableInterface $value The value to validate.
* @throws InvalidValueException The value is invalid.
*/
private function validateValue( $value ): void
{
/**
* @var ValueValidatorInterface $valueValidator
*/
$valueValidator = static::getFlagableState()->getValueValidator();
$valueValidator->validate( $this, static::getFlagableState()->getReflectedFlags(), static::getFlagableState()->getMaxValue(), $value );
if ( false === $valueValidator->succeeded() )
{
throw ( new InvalidValueException( 'Invalid value.' ) )
->withErrorMessages( $valueValidator->getErrorMessages() );
}
}
/**
* Gets the extracted value of a value.
* @param mixed $value The value to extract.
* @return int The extracted value.
*/
private function getExtractedValue( $value ): int
{
$returnValue = null;
if ( true === is_int( $value ) )
{
$returnValue = $value;
}
if ( true === is_string( $value ) )
{
$extractedValue = static::NONE;
/**
* @var string $explodedValue
*/
foreach ( explode( '|', $value ) as $explodedValue )
{
if ( false === ctype_digit( $explodedValue ) )
{
$extractedValue |= static::getFlagableState()->getReflectedFlags()[ $explodedValue ];
continue;
}
$extractedValue |= (int) $explodedValue;
}
$returnValue = $extractedValue;
}
return $returnValue ?? $value->getValue();
}
/**
* Determines if a value has been set.
* @param int $value The value to check if it has been set.
* @return bool True if the value has been set, false otherwise.
*/
private function unvalidatedHas( int $value ): bool
{
return ( $this->value & $value ) === $value;
}
/**
* Sets a flag.
* @param int $value The flag to set.
*/
private function unvalidatedSet( int $value ): void
{
$this->value |= $value;
}
/**
* Unsets a flag.
* @param int $value The flag to unset.
*/
private function unvalidatedUnset( int $value ): void
{
$this->value &= ~$value;
}
/**
* Switches a flag.
* @param int $value The flag to switch.
*/
private function unvalidatedSwitch( int $value ): void
{
$this->value ^= $value;
}
/**
* {@inheritdoc}
*/
final public function has( $value ): bool
{
$this->validateValue( $value );
return $this->unvalidatedHas( $this->getExtractedValue( $value ) );
}
/**
* {@inheritdoc}
*/
final public function set( $value ): FlagableInterface
{
$this->validateValue( $value );
$this->unvalidatedSet( $this->getExtractedValue( $value ) );
return $this;
}
/**
* {@inheritdoc}
*/
final public function unset( $value ): FlagableInterface
{
$this->validateValue( $value );
$this->unvalidatedUnset( $this->getExtractedValue( $value ) );
return $this;
}
/**
* {@inheritdoc}
*/
final public function switch( $value ): FlagableInterface
{
$this->validateValue( $value );
$this->unvalidatedSwitch( $this->getExtractedValue( $value ) );
return $this;
}
/**
* {@inheritdoc}
*/
final public function getIterator(): Traversable
{
if ( static::NONE === $this->value )
{
yield new static;
return;
}
/**
* @var int $reflectedFlagValue
*/
foreach ( static::getFlagableState()->getReflectedFlags() as $reflectedFlagValue )
{
if ( static::NONE !== $reflectedFlagValue && true === $this->unvalidatedHas( $reflectedFlagValue ) )
{
yield new static( $reflectedFlagValue );
}
}
}
}