21
21
* }
22
22
*
23
23
* // Usage:
24
+ * $enum = PersonEnum::from('first'); // Creates instance with value 'first'
25
+ * $enum = PersonEnum::tryFrom('invalid'); // Returns null
24
26
* $enum = PersonEnum::firstName(); // Creates instance with value 'first'
25
- * $enum->isFirstName(); // Returns true
27
+ * $enum->name; // 'FIRST_NAME'
28
+ * $enum->value; // 'first'
26
29
* $enum->equals('first'); // Returns true
27
30
* $enum->is(PersonEnum::firstName()); // Returns true
31
+ * PersonEnum::cases(); // Returns array of all enum instances
28
32
*/
29
33
abstract class AbstractEnum
30
34
{
31
35
/**
32
- * @var string|int|float The value of the enum instance
36
+ * @var string|int The value of the enum instance
33
37
*/
34
38
private $ value ;
35
39
36
40
/**
37
- * @var array<string, array<string, string|int|float>> Cache for reflection data
41
+ * @var string The name of the enum constant
42
+ */
43
+ private $ name ;
44
+
45
+ /**
46
+ * @var array<string, array<string, string|int>> Cache for reflection data
38
47
*/
39
48
private static $ cache = [];
40
49
50
+ /**
51
+ * @var array<string, array<string, self>> Cache for enum instances
52
+ */
53
+ private static $ instances = [];
54
+
41
55
/**
42
56
* Constructor is private to ensure instances are created through static methods
43
57
*
44
- * @param string|int|float $value The enum value
58
+ * @param string|int $value The enum value
59
+ * @param string $name The constant name
45
60
*/
46
- private function __construct ($ value )
61
+ private function __construct ($ value, string $ name )
47
62
{
48
63
$ this ->value = $ value ;
64
+ $ this ->name = $ name ;
65
+ }
66
+
67
+ /**
68
+ * Magic getter to provide read-only access to properties
69
+ *
70
+ * @param string $property The property name
71
+ * @return mixed
72
+ * @throws BadMethodCallException If property doesn't exist
73
+ */
74
+ public function __get (string $ property )
75
+ {
76
+ if ($ property === 'value ' || $ property === 'name ' ) {
77
+ return $ this ->$ property ;
78
+ }
79
+
80
+ throw new BadMethodCallException (
81
+ sprintf ('Property %s::%s does not exist ' , static ::class, $ property )
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Magic setter to prevent property modification
87
+ *
88
+ * @param string $property The property name
89
+ * @param mixed $value The value to set
90
+ * @throws BadMethodCallException Always, as enum properties are read-only
91
+ */
92
+ public function __set (string $ property , $ value ): void
93
+ {
94
+ throw new BadMethodCallException (
95
+ sprintf ('Cannot modify property %s::%s - enum properties are read-only ' , static ::class, $ property )
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Create an enum instance from a value, throws exception if invalid
101
+ *
102
+ * @param string|int $value The enum value
103
+ * @return static
104
+ * @throws InvalidArgumentException If the value is not valid
105
+ */
106
+ public static function from ($ value ): self
107
+ {
108
+ $ instance = self ::tryFrom ($ value );
109
+ if ($ instance === null ) {
110
+ throw new InvalidArgumentException (
111
+ sprintf ('%s is not a valid backing value for enum %s ' , (string ) $ value , static ::class)
112
+ );
113
+ }
114
+ return $ instance ;
49
115
}
50
116
51
117
/**
52
- * Get the value of the enum instance
118
+ * Try to create an enum instance from a value, returns null if invalid
53
119
*
54
- * @return string|int|float
120
+ * @param string|int $value The enum value
121
+ * @return static|null
55
122
*/
56
- public function getValue ()
123
+ public static function tryFrom ( $ value ): ? self
57
124
{
58
- return $ this ->value ;
125
+ $ constants = self ::getConstants ();
126
+ foreach ($ constants as $ name => $ constantValue ) {
127
+ if ($ constantValue === $ value ) {
128
+ return self ::getInstance ($ constantValue , $ name );
129
+ }
130
+ }
131
+ return null ;
132
+ }
133
+
134
+ /**
135
+ * Get all enum cases
136
+ *
137
+ * @return static[]
138
+ */
139
+ public static function cases (): array
140
+ {
141
+ $ cases = [];
142
+ $ constants = self ::getConstants ();
143
+ foreach ($ constants as $ name => $ value ) {
144
+ $ cases [] = self ::getInstance ($ value , $ name );
145
+ }
146
+ return $ cases ;
59
147
}
60
148
61
149
/**
62
150
* Check if this enum has the same value as the given value
63
151
*
64
- * @param string|int|float| self $other The value or enum to compare
152
+ * @param string|int|self $other The value or enum to compare
65
153
* @return bool
66
154
*/
67
155
public function equals ($ other ): bool
@@ -81,13 +169,13 @@ public function equals($other): bool
81
169
*/
82
170
public function is (self $ other ): bool
83
171
{
84
- return get_class ( $ this ) === get_class ( $ other) && $ this -> value === $ other -> getValue ();
172
+ return $ this === $ other; // Since we're using singletons, we can use identity comparison
85
173
}
86
174
87
175
/**
88
176
* Get all valid values for this enum
89
177
*
90
- * @return array<string, string|int|float >
178
+ * @return array<string, string|int>
91
179
*/
92
180
public static function getValues (): array
93
181
{
@@ -97,7 +185,7 @@ public static function getValues(): array
97
185
/**
98
186
* Check if a value is valid for this enum
99
187
*
100
- * @param string|int|float $value The value to check
188
+ * @param string|int $value The value to check
101
189
* @return bool
102
190
*/
103
191
public static function isValidValue ($ value ): bool
@@ -106,28 +194,48 @@ public static function isValidValue($value): bool
106
194
}
107
195
108
196
/**
109
- * Create an enum instance from a value
197
+ * Create an enum instance from a value (deprecated, use from() instead)
110
198
*
111
- * @param string|int|float $value The enum value
199
+ * @param string|int $value The enum value
112
200
* @return static
113
201
* @throws InvalidArgumentException If the value is not valid
202
+ * @deprecated Use from() method instead
114
203
*/
115
204
public static function fromValue ($ value ): self
116
205
{
117
- if (!self ::isValidValue ($ value )) {
118
- throw new InvalidArgumentException (
119
- sprintf ('Invalid value "%s" for enum %s ' , (string ) $ value , static ::class)
120
- );
121
- }
206
+ return self ::from ($ value );
207
+ }
122
208
209
+ /**
210
+ * Get or create a singleton instance for the given value and name
211
+ *
212
+ * @param string|int $value The enum value
213
+ * @param string $name The constant name
214
+ * @return static
215
+ * @phpstan-return static
216
+ */
217
+ private static function getInstance ($ value , string $ name ): self
218
+ {
123
219
$ className = static ::class;
124
- return new $ className ($ value );
220
+ $ key = $ name ;
221
+
222
+ if (!isset (self ::$ instances [$ className ])) {
223
+ self ::$ instances [$ className ] = [];
224
+ }
225
+
226
+ if (!isset (self ::$ instances [$ className ][$ key ])) {
227
+ $ instance = new $ className ($ value , $ name );
228
+ self ::$ instances [$ className ][$ key ] = $ instance ;
229
+ }
230
+
231
+ /** @var static */
232
+ return self ::$ instances [$ className ][$ key ];
125
233
}
126
234
127
235
/**
128
236
* Get all constants for this enum class
129
237
*
130
- * @return array<string, string|int|float >
238
+ * @return array<string, string|int>
131
239
*/
132
240
protected static function getConstants (): array
133
241
{
@@ -142,7 +250,7 @@ protected static function getConstants(): array
142
250
foreach ($ constants as $ name => $ value ) {
143
251
if (
144
252
preg_match ('/^[A-Z][A-Z0-9_]*$/ ' , $ name )
145
- && (is_string ($ value ) || is_int ($ value ) || is_float ( $ value ) )
253
+ && (is_string ($ value ) || is_int ($ value ))
146
254
) {
147
255
$ enumConstants [$ name ] = $ value ;
148
256
}
@@ -193,8 +301,7 @@ public static function __callStatic(string $name, array $arguments)
193
301
$ constants = self ::getConstants ();
194
302
195
303
if (isset ($ constants [$ constantName ])) {
196
- $ className = static ::class;
197
- return new $ className ($ constants [$ constantName ]);
304
+ return self ::getInstance ($ constants [$ constantName ], $ constantName );
198
305
}
199
306
200
307
throw new BadMethodCallException (
0 commit comments