Skip to content

Commit 13532cd

Browse files
committed
Singleton
1 parent 8eca68b commit 13532cd

File tree

2 files changed

+126
-79
lines changed

2 files changed

+126
-79
lines changed

src/MabeEnum/Enum.php

Lines changed: 116 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
abstract class MabeEnum_Enum
1111
{
1212
/**
13-
* The current selected value
13+
* The selected value
1414
* @var null|scalar
1515
*/
1616
private $value;
@@ -22,59 +22,34 @@ abstract class MabeEnum_Enum
2222
private $ordinal;
2323

2424
/**
25-
* An array of available constants
26-
* @var null|array
25+
* An array of available constants by class
26+
* @var array ["$class" => ["$const" => $value, ...], ...]
2727
*/
28-
private $constants;
28+
private static $constants = array();
29+
30+
/**
31+
* Already instantiated enums
32+
* @param array ["$class.$value" => MabeEnum_Enum, ...]
33+
*/
34+
private static $instances = array();
2935

3036
/**
3137
* Constructor
3238
*
3339
* @param scalar $value The value to select
34-
* @throws InvalidArgumentException
40+
* @throws InvalidArgumentException On an unknwon or invalid value
41+
* @throws LogicException On ambiguous constant values
3542
*/
36-
public function __construct($value)
43+
final private function __construct($value)
3744
{
38-
$reflectionClass = new ReflectionClass($this);
39-
$constants = $reflectionClass->getConstants();
40-
41-
// Constant values needs to be unique
42-
if (count($constants) > count(array_unique($constants))) {
43-
$ambiguous = array();
44-
foreach (array_count_values($constants) as $constValue => $countValue) {
45-
if ($countValue < 2) {
46-
continue;
47-
}
48-
$ambiguous[] = $constValue;
49-
}
50-
throw new LogicException(sprintf(
51-
'All possible values needs to be unique. The following are ambiguous: %s',
52-
"'" . implode("', '", $ambiguous) . "'"
53-
));
54-
}
55-
56-
// This is required to make sure that constants of base classes will be the first
57-
while ( ($reflectionClass = $reflectionClass->getParentClass()) ) {
58-
$constants = $reflectionClass->getConstants() + $constants;
59-
}
60-
$this->constants = $constants;
61-
6245
// find and set the given value
6346
// set the defined value because of non strict comparison
64-
$const = array_search($value, $this->constants);
47+
$constants = static::getConstants();
48+
$const = array_search($value, $constants);
6549
if ($const === false) {
6650
throw new InvalidArgumentException("Unknown value '{$value}'");
6751
}
68-
$this->value = $this->constants[$const];
69-
}
70-
71-
/**
72-
* Get all available constants
73-
* @return array
74-
*/
75-
final public function getConstants()
76-
{
77-
return $this->constants;
52+
$this->value = $constants[$const];
7853
}
7954

8055
/**
@@ -92,9 +67,13 @@ final public function getValue()
9267
*/
9368
final public function getName()
9469
{
95-
return array_search($this->value, $this->constants, true);
70+
return array_search($this->value, $this::getConstants(), true);
9671
}
9772

73+
/**
74+
* Get the ordinal number of the selected value
75+
* @return int
76+
*/
9877
final public function getOrdinal()
9978
{
10079
if ($this->ordinal !== null) {
@@ -104,7 +83,7 @@ final public function getOrdinal()
10483
// detect ordinal
10584
$ordinal = 0;
10685
$value = $this->value;
107-
foreach ($this->constants as $constValue) {
86+
foreach ($this::getConstants() as $constValue) {
10887
if ($value === $constValue) {
10988
break;
11089
}
@@ -125,25 +104,112 @@ final public function __toString()
125104
return (string) $this->value;
126105
}
127106

107+
/**
108+
* Get an enum of the given value
109+
*
110+
* @param scalar $value
111+
* @return MabeEnum_Enum
112+
* @throws InvalidArgumentException On an unknwon or invalid value
113+
* @throws LogicException On ambiguous constant values
114+
*/
115+
static public function get($value)
116+
{
117+
$class = get_called_class();
118+
$id = $class . '.' . $value;
119+
if (isset(self::$instances[$id])) {
120+
return self::$instances[$id];
121+
}
122+
123+
$instance = new $class($value);
124+
self::$instances[$id] = $instance;
125+
return $instance;
126+
}
127+
128+
/**
129+
* Clears all instantiated enums
130+
*
131+
* NOTE: This can break singleton behavior ... use it with caution!
132+
*
133+
* @param null|string $class
134+
* @return void
135+
*/
136+
final static function clear($class = null)
137+
{
138+
$class = get_called_class();
139+
140+
// clear instantiated enums
141+
foreach (self::$enums as $id => $enum) {
142+
if (strcasecmp($class . '.', $id) === 0) {
143+
unset(self::$enums[$id]);
144+
}
145+
}
146+
147+
// clear constants buffer
148+
foreach (self::$constants as $constantsClass => & $constants) {
149+
if (strcasecmp($class, $constantsClass) === 0) {
150+
unset(self::$constants[$constantsClass]);
151+
break;
152+
}
153+
}
154+
}
155+
156+
/**
157+
* Get all available constants
158+
* @return array
159+
* @throws LogicException On ambiguous constant values
160+
*/
161+
final static public function getConstants()
162+
{
163+
$class = get_called_class();
164+
if (isset(self::$constants[$class])) {
165+
return self::$constants[$class];
166+
}
167+
168+
$reflection = new ReflectionClass($class);
169+
$constants = $reflection->getConstants();
170+
171+
// Constant values needs to be unique
172+
if (count($constants) > count(array_unique($constants))) {
173+
$ambiguous = array();
174+
foreach (array_count_values($constants) as $constValue => $countValue) {
175+
if ($countValue < 2) {
176+
continue;
177+
}
178+
$ambiguous[] = $constValue;
179+
}
180+
throw new LogicException(sprintf(
181+
'All possible values needs to be unique. The following are ambiguous: %s',
182+
"'" . implode("', '", $ambiguous) . "'"
183+
));
184+
}
185+
186+
// This is required to make sure that constants of base classes will be the first
187+
while ( ($reflection = $reflection->getParentClass()) ) {
188+
$constants = $reflection->getConstants() + $constants;
189+
}
190+
191+
self::$constants[$class] = $constants;
192+
return $constants;
193+
}
194+
128195
/**
129196
* Instantiate an enum by a name of a constant.
130197
*
131198
* This will be called automatically on calling a method
132199
* with the same name of a defined constant.
133200
*
134-
* NOTE: THIS WORKS FOR PHP >= 5.3 ONLY
135-
*
136201
* @param string $const The name of the constant to instantiate the enum with
137202
* @param array $args There should be no arguments
138-
* @throws BadMethodCallException
203+
* @throws BadMethodCallException On an unknown constant name (method name)
204+
* @throws LogicException On ambiguous constant values
139205
*/
140206
final public static function __callStatic($const, array $args)
141207
{
142-
$class = get_called_class();
143-
$classConst = $class . '::' . $const;
208+
$classConst = 'static::' . $const;
144209
if (!defined($classConst)) {
145-
throw new BadMethodCallException($classConst . ' not defined');
210+
$class = get_called_class();
211+
throw new BadMethodCallException($class . '::' . $const . ' not defined');
146212
}
147-
return new $class(constant($classConst));
213+
return static::get(constant($classConst));
148214
}
149215
}

tests/MabeEnumTest/EnumTest.php

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,76 +9,57 @@
99
*/
1010
class MabeEnumTest_EnumTest extends PHPUnit_Framework_TestCase
1111
{
12-
public function testEnumWithDefaultValue()
13-
{
14-
$enum = new MabeEnumTest_TestAsset_EnumWithDefaultValue();
15-
16-
$this->assertSame(
17-
array(
18-
'ONE' => 1,
19-
'TWO' => 2,
20-
),
21-
$enum->getConstants()
22-
);
23-
24-
$this->assertSame(1, $enum->getValue());
25-
$this->assertSame('1', $enum->__toString());
26-
27-
$this->assertSame('ONE', $enum->getName());
28-
$this->assertSame(0, $enum->getOrdinal());
29-
}
30-
3112
public function testGetNameReturnsConstantNameOfCurrentValue()
3213
{
33-
$enum = new MabeEnumTest_TestAsset_EnumWithoutDefaultValue(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
14+
$enum = MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
3415
$this->assertSame('ONE', $enum->getName());
3516
}
3617

3718
public function testToStringMagicMethodReturnsValueAsString()
3819
{
39-
$enum = new MabeEnumTest_TestAsset_EnumWithoutDefaultValue(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
20+
$enum = MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
4021
$this->assertSame('1', $enum->__toString());
4122
}
4223

4324
public function testEnumInheritance()
4425
{
45-
$enum = new MabeEnumTest_TestAsset_EnumInheritance(MabeEnumTest_TestAsset_EnumInheritance::ONE);
26+
$enum = MabeEnumTest_TestAsset_EnumInheritance::get(MabeEnumTest_TestAsset_EnumInheritance::ONE);
4627
$this->assertSame(array(
4728
'ONE' => 1,
4829
'TWO' => 2,
4930
'INHERITANCE' => 'Inheritance'
50-
), $enum->getConstants());
31+
), $enum::getConstants());
5132
$this->assertSame(MabeEnumTest_TestAsset_EnumInheritance::ONE, $enum->getValue());
5233
$this->assertSame(0, $enum->getOrdinal());
5334

54-
$enum = new MabeEnumTest_TestAsset_EnumInheritance(MabeEnumTest_TestAsset_EnumInheritance::INHERITANCE);
35+
$enum = MabeEnumTest_TestAsset_EnumInheritance::get(MabeEnumTest_TestAsset_EnumInheritance::INHERITANCE);
5536
$this->assertSame(MabeEnumTest_TestAsset_EnumInheritance::INHERITANCE, $enum->getValue());
5637
$this->assertSame(2, $enum->getOrdinal());
5738
}
5839

5940
public function testConstructorStrictValue()
6041
{
61-
$enum = new MabeEnumTest_TestAsset_EnumWithoutDefaultValue(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
42+
$enum = MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::ONE);
6243
$this->assertSame(1, $enum->getValue());
6344
$this->assertSame(0, $enum->getOrdinal());
6445
}
6546

6647
public function testConstuctorNonStrictValue()
6748
{
68-
$enum = new MabeEnumTest_TestAsset_EnumWithoutDefaultValue((string)MabeEnumTest_TestAsset_EnumWithoutDefaultValue::TWO);
49+
$enum = MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get((string)MabeEnumTest_TestAsset_EnumWithoutDefaultValue::TWO);
6950
$this->assertSame(2, $enum->getValue());
7051
$this->assertSame(1, $enum->getOrdinal());
7152
}
7253

7354
public function testConstructorInvalidValueThrowsInvalidArgumentException()
7455
{
7556
$this->setExpectedException('InvalidArgumentException');
76-
new MabeEnumTest_TestAsset_EnumWithoutDefaultValue('unknown');
57+
MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get('unknown');
7758
}
7859

7960
public function testCallingGetOrdinalTwoTimesWillResultTheSameValue()
8061
{
81-
$enum = new MabeEnumTest_TestAsset_EnumWithoutDefaultValue(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::TWO);
62+
$enum = MabeEnumTest_TestAsset_EnumWithoutDefaultValue::get(MabeEnumTest_TestAsset_EnumWithoutDefaultValue::TWO);
8263
$this->assertSame(1, $enum->getOrdinal());
8364
$this->assertSame(1, $enum->getOrdinal());
8465
}
@@ -107,6 +88,6 @@ public function testInstantiateUsingMagicMethodThrowsBadMethodCallException()
10788
public function testAmbuguousConstantsThrowsLogicException()
10889
{
10990
$this->setExpectedException('LogicException');
110-
new MabeEnumTest_TestAsset_EnumAmbiguous(MabeEnumTest_TestAsset_EnumAmbiguous::AMBIGUOUS1);
91+
MabeEnumTest_TestAsset_EnumAmbiguous::get(MabeEnumTest_TestAsset_EnumAmbiguous::AMBIGUOUS1);
11192
}
11293
}

0 commit comments

Comments
 (0)