-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathEnumeration.php
More file actions
229 lines (202 loc) · 7.02 KB
/
Enumeration.php
File metadata and controls
229 lines (202 loc) · 7.02 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
<?php
declare(strict_types=1);
namespace Dbalabka\Enumeration;
use Dbalabka\Enumeration\Exception\EnumerationException;
use Dbalabka\Enumeration\Exception\InvalidArgumentException;
use Dbalabka\StaticConstructorLoader\StaticConstructorInterface;
use ReflectionClass;
use Serializable;
use function array_search;
use function get_class_vars;
use function sprintf;
/**
* Inspired by https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types
* Implemented using https://github.com/dotnet-architecture/eShopOnContainers/blob/8960db40d43d79ad475799dedfe311ebc49cab99/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs
* Enumerated types RFC: https://wiki.php.net/rfc/enum
* Each static property should be declared as read-only, unfortunately, PHP does not support this (see RFC https://wiki.php.net/rfc/readonly_properties)
*
* @author Dmitry Balabka <dmitry.balabka@gmail.com>
*/
abstract class Enumeration implements StaticConstructorInterface, Serializable
{
const INITIAL_ORDINAL = 0;
/**
* @var int|null
*/
protected $ordinal;
/** @var array */
private static $initializedEnums = [];
/**
* @throws EnumerationException
*/
final public static function __constructStatic() : void
{
if (self::class === static::class) {
return;
}
static::initialize();
}
final public static function initialize(): void
{
if (isset(self::$initializedEnums[static::class])) {
return;
}
/**
* Using reflections we can enforce the strict rules of enums declaration.
* It should not rise performance issue because enumeration class initialization executes only once.
*/
$classReflection = new ReflectionClass(static::class);
if (!$classReflection->isAbstract() && !$classReflection->isFinal()) {
throw new EnumerationException('Enumeration class should be declared as final');
}
$method = $classReflection->getConstructor();
if ($method && $method->isPublic()) {
throw new EnumerationException('Enumeration class constructor should not be public');
}
static::initializeValues();
static::initializeOrdinals();
self::$initializedEnums[static::class] = true;
}
final protected static function initializeOrdinals() : void
{
$ordinal = static::INITIAL_ORDINAL;
foreach (static::values() as $value) {
$value->ordinal = $ordinal++;
}
}
/**
* Override this method to manually initialize Enum values. Useful when __construct() accepts at least one argument.
* Enum objects does not have any properties by default.
*/
protected static function initializeValues() : void
{
/**
* TODO: Unfortunately, it is impossible to avoid class reflection here.
* The reflection caching here should not give major performance benefit.
* It should be properly benchmarked.
*/
if ((new ReflectionClass(static::class))->isAbstract()) {
return;
}
$firstEnumItem = new static();
$staticVars = static::getEnumStaticVars($firstEnumItem);
$firstEnumName = key($staticVars);
static::$$firstEnumName = $firstEnumItem;
array_shift($staticVars);
foreach ($staticVars as $name => $value) {
static::$$name = new static();
}
}
final protected static function getEnumStaticVars(Enumeration $enum): array
{
$nonStaticVars = get_object_vars($enum);
$allVars = get_class_vars(static::class);
$staticVars = array_diff_key($allVars, $nonStaticVars);
unset($staticVars['initializedEnums']);
return $staticVars;
}
/**
* @return static[]
*/
final public static function values() : array
{
return array_filter(
get_class_vars(static::class),
static function ($value) {
return $value instanceof Enumeration;
}
);
}
/**
* @return static
*/
final public static function valueOf(string $name)
{
if ($value = static::values()[$name] ?? null) {
return $value;
}
throw new InvalidArgumentException(sprintf('Invalid "%s" enum item name', $name));
}
/**
* Override default constructor to set object properties for each Enum value.
*/
protected function __construct()
{
}
/**
* @throws EnumerationException
*/
final public function ordinal() : int
{
if (null === $this->ordinal) {
// When we call ordinal() in constructor the ordinal isn't initialized yet.
// It is the only one case when ordinal isn't initialized.
$staticVars = static::getEnumStaticVars($this);
$ordinal = static::INITIAL_ORDINAL;
foreach ($staticVars as $var) {
if ($var === null || $var === $this) {
$this->ordinal = $ordinal;
break;
}
$ordinal++;
}
}
if ($this->ordinal === null) {
throw new EnumerationException('Ordinal initialization failed. Enum does not contain any static variables');
}
return $this->ordinal;
}
/**
* @throws EnumerationException
*/
final public function compareTo(self $enum) : int
{
return $this->ordinal() - $enum->ordinal();
}
/**
* Override this method to custom "programmer-friendly" string representation of Enum value.
* It returns Enum name by default.
*/
public function __toString() : string
{
return $this->name();
}
final public function name() : string
{
$name = array_search($this, static::values(), true);
if (false === $name) {
throw new EnumerationException(
sprintf(
'Can not find $this in %s::values(). ' .
'It seems that the static property was overwritten. This is not allowed.',
get_class($this)
)
);
}
return $name;
}
final public function __clone()
{
throw new EnumerationException('Enum cloning is not allowed');
}
/**
* Serialization is not allowed right now. It is not possible to properly serialize the singleton.
* See the documentation for workaround.
*/
final public function __serialize()
{
throw new EnumerationException('Enum serialization is not allowed');
}
final public function __unserialize($data)
{
throw new EnumerationException('Enum unserialization is not allowed');
}
final public function serialize()
{
throw new EnumerationException('Enum serialization is not allowed');
}
final public function unserialize($data)
{
throw new EnumerationException('Enum unserialization is not allowed');
}
}