Skip to content

Commit 82129c3

Browse files
committed
refactor: makes AbstractEnum act more like Enum
1 parent 9faab04 commit 82129c3

File tree

1 file changed

+132
-25
lines changed

1 file changed

+132
-25
lines changed

src/Common/AbstractEnum.php

Lines changed: 132 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,47 +21,135 @@
2121
* }
2222
*
2323
* // Usage:
24+
* $enum = PersonEnum::from('first'); // Creates instance with value 'first'
25+
* $enum = PersonEnum::tryFrom('invalid'); // Returns null
2426
* $enum = PersonEnum::firstName(); // Creates instance with value 'first'
25-
* $enum->isFirstName(); // Returns true
27+
* $enum->name; // 'FIRST_NAME'
28+
* $enum->value; // 'first'
2629
* $enum->equals('first'); // Returns true
2730
* $enum->is(PersonEnum::firstName()); // Returns true
31+
* PersonEnum::cases(); // Returns array of all enum instances
2832
*/
2933
abstract class AbstractEnum
3034
{
3135
/**
32-
* @var string|int|float The value of the enum instance
36+
* @var string|int The value of the enum instance
3337
*/
3438
private $value;
3539

3640
/**
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
3847
*/
3948
private static $cache = [];
4049

50+
/**
51+
* @var array<string, array<string, self>> Cache for enum instances
52+
*/
53+
private static $instances = [];
54+
4155
/**
4256
* Constructor is private to ensure instances are created through static methods
4357
*
44-
* @param string|int|float $value The enum value
58+
* @param string|int $value The enum value
59+
* @param string $name The constant name
4560
*/
46-
private function __construct($value)
61+
private function __construct($value, string $name)
4762
{
4863
$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;
49115
}
50116

51117
/**
52-
* Get the value of the enum instance
118+
* Try to create an enum instance from a value, returns null if invalid
53119
*
54-
* @return string|int|float
120+
* @param string|int $value The enum value
121+
* @return static|null
55122
*/
56-
public function getValue()
123+
public static function tryFrom($value): ?self
57124
{
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;
59147
}
60148

61149
/**
62150
* Check if this enum has the same value as the given value
63151
*
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
65153
* @return bool
66154
*/
67155
public function equals($other): bool
@@ -81,13 +169,13 @@ public function equals($other): bool
81169
*/
82170
public function is(self $other): bool
83171
{
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
85173
}
86174

87175
/**
88176
* Get all valid values for this enum
89177
*
90-
* @return array<string, string|int|float>
178+
* @return array<string, string|int>
91179
*/
92180
public static function getValues(): array
93181
{
@@ -97,7 +185,7 @@ public static function getValues(): array
97185
/**
98186
* Check if a value is valid for this enum
99187
*
100-
* @param string|int|float $value The value to check
188+
* @param string|int $value The value to check
101189
* @return bool
102190
*/
103191
public static function isValidValue($value): bool
@@ -106,28 +194,48 @@ public static function isValidValue($value): bool
106194
}
107195

108196
/**
109-
* Create an enum instance from a value
197+
* Create an enum instance from a value (deprecated, use from() instead)
110198
*
111-
* @param string|int|float $value The enum value
199+
* @param string|int $value The enum value
112200
* @return static
113201
* @throws InvalidArgumentException If the value is not valid
202+
* @deprecated Use from() method instead
114203
*/
115204
public static function fromValue($value): self
116205
{
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+
}
122208

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+
{
123219
$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];
125233
}
126234

127235
/**
128236
* Get all constants for this enum class
129237
*
130-
* @return array<string, string|int|float>
238+
* @return array<string, string|int>
131239
*/
132240
protected static function getConstants(): array
133241
{
@@ -142,7 +250,7 @@ protected static function getConstants(): array
142250
foreach ($constants as $name => $value) {
143251
if (
144252
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))
146254
) {
147255
$enumConstants[$name] = $value;
148256
}
@@ -193,8 +301,7 @@ public static function __callStatic(string $name, array $arguments)
193301
$constants = self::getConstants();
194302

195303
if (isset($constants[$constantName])) {
196-
$className = static::class;
197-
return new $className($constants[$constantName]);
304+
return self::getInstance($constants[$constantName], $constantName);
198305
}
199306

200307
throw new BadMethodCallException(

0 commit comments

Comments
 (0)