Skip to content

Commit 7abfc3d

Browse files
committedFeb 21, 2019
- Added overridable defineInstances method to allow bulk instance definition
- Updated documentation
1 parent 5a5a99a commit 7abfc3d

File tree

4 files changed

+120
-20
lines changed

4 files changed

+120
-20
lines changed
 

‎docs/enums.md

+52-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class FooBarEnum implements EnumInterface
3232
}
3333
}
3434

35-
FooBarEnum::FOO() === FooBarEnum::FOO() // true
35+
FooBarEnum::FOO() === FooBarEnum::FOO(); // true
3636
```
3737

3838
If you want to store extra data on your Enum instances, you can add a
@@ -75,6 +75,9 @@ class FooBarEnum implements EnumInterface
7575
echo FooBarEnum::FOO()->getMessage(); // outputs 'foo mama'
7676
```
7777

78+
Subclasses
79+
----------
80+
7881
Enum classes can have subclasses as well, but the following caveats apply:
7982
- although the factory methods are inherited from the parent Enum, these
8083
still produce instances of the class on which they are called, so
@@ -85,3 +88,51 @@ Enum classes can have subclasses as well, but the following caveats apply:
8588
and any extra parameters not accepted by the parent constructor _must
8689
be optional_, so that the data from the parent factory methods can be
8790
accepted by the child constructor.
91+
92+
Bulk definitions
93+
----------------
94+
95+
Some enum classes may need a large number of instances, or even instances
96+
depending on some external definition, such as a CSV file. In these
97+
cases, it will be tedious or even error-prone to create a separate
98+
factory method for each of the instances. For these cases, you can
99+
override the `defineInstances` method of `EnumTrait`. Inside this method
100+
you can call `define` multiple times to create as many instances as
101+
you need, like so:
102+
103+
```php
104+
<?php
105+
106+
use SolidPhp\ValueObjects\Enum\EnumTrait;
107+
use SolidPhp\ValueObjects\Enum\EnumInterface;
108+
109+
class Country implements EnumInterface
110+
{
111+
use EnumTrait;
112+
113+
protected static function defineInstances() : void
114+
{
115+
// countries.csv:
116+
// US;United States of America
117+
// NL;Netherlands
118+
// UK;United Kingdom
119+
// ...
120+
$countryDefinitions = explode("\n", file_get_contents(__DIR__ . 'countries.csv'));
121+
122+
foreach ($countryDefinitions as $countryDefinition) {
123+
[$code, $name] = explode(';', $countryDefinition);
124+
self::define($code, $name);
125+
}
126+
}
127+
}
128+
```
129+
130+
If you define your instances like this, the following caveats apply:
131+
132+
- You can no longer use `define` in other methods. Any factory methods
133+
you write will need to use `instance` or `instances` to get their
134+
instances.
135+
- Because of this, if the class has any child classes, they also need
136+
to override this method and use it to define their instances. As a consequence,
137+
they can choose whether or not to include the parent class instances
138+
for themselves by calling or not calling `parent::defineInstances()` there.

‎src/Enum/EnumTrait.php

+32-19
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* After using this Trait, the class can define public static "factory" methods to define its instances.
1111
*
1212
* For example, here is a very simple implementation:
13-
* ```
13+
* ```php
1414
* class FooBarEnum implements EnumInterface
1515
* {
1616
* use EnumTrait;
@@ -72,7 +72,7 @@ trait EnumTrait /* implements EnumInterface */
7272
{
7373
private static $instancesById;
7474

75-
/** @var array */
75+
/** @var int[] */
7676
private static $allInstancesInitialized = [];
7777

7878
/**
@@ -99,14 +99,30 @@ final protected static function define(string $id, ...$constructorArguments)
9999
return self::$instancesById[static::class][$id];
100100
}
101101

102+
/**
103+
* This method is called the first time it's needed.
104+
* It should call `define` for each instance to be created.
105+
*
106+
* The default behavior is to call every public factory method in the class, with
107+
* the expectation that those methods will call `define`. Override this method to provide
108+
* different behavior, for instance loading instances parametrically from an array or file.
109+
*
110+
* If you override this method, the class must be final, because child classes won't be able
111+
* to add instances.
112+
*/
113+
protected static function defineInstances(): void
114+
{
115+
call_all_public_nullary_factory_methods(static::class);
116+
}
117+
102118
/**
103119
* @param string $id
104120
* @param bool $throwIfNotFound
105121
* @return null|$this
106122
*/
107123
final public static function instance(string $id, bool $throwIfNotFound = false)
108124
{
109-
static::_ensureAllInstancesInitialized(static::class);
125+
static::_ensureAllInstancesInitialized();
110126

111127
if (isset(self::$instancesById[static::class][$id])) {
112128
return self::$instancesById[static::class][$id];
@@ -124,42 +140,39 @@ final public static function instance(string $id, bool $throwIfNotFound = false)
124140
*/
125141
final public static function instances(): array
126142
{
127-
static::_ensureAllInstancesInitialized(static::class);
143+
static::_ensureAllInstancesInitialized();
128144

129145
return array_values(self::$instancesById[static::class]);
130146
}
131147

132148
final public function getId(): string
133149
{
134-
static::_ensureAllInstancesInitialized(static::class);
150+
static::_ensureAllInstancesInitialized();
135151

136-
foreach (self::$instancesById[static::class] as $id => $instance) {
137-
if ($instance === $this) {
138-
return $id;
139-
}
140-
}
141-
142-
return null;
152+
return array_search($this, self::$instancesById[static::class], true);
143153
}
144154

145-
private static function _ensureAllInstancesInitialized(string $class): void
155+
private static function _ensureAllInstancesInitialized(): void
146156
{
147-
if (!isset(self::$allInstancesInitialized[$class])) {
148-
initialize_all_enum_instances($class);
149-
self::$allInstancesInitialized[$class] = true;
157+
static $_isInitializing;
158+
if (!$_isInitializing && !isset(self::$allInstancesInitialized[static::class])) {
159+
$_isInitializing = true;
160+
static::defineInstances();
161+
$_isInitializing = false;
162+
self::$allInstancesInitialized[static::class] = true;
150163
}
151164
}
152165
}
153166

154-
function initialize_all_enum_instances(string $enumClass): void
167+
function call_all_public_nullary_factory_methods(string $enumClass): void
155168
{
156169
try {
157170
$reflectionClass = new \ReflectionClass($enumClass);
158171

159172
/** @var \ReflectionMethod[] $factoryMethods */
160173
$factoryMethods = array_filter(
161174
$reflectionClass->getMethods(),
162-
'SolidPhp\ValueObjects\Enum\is_enum_factory_method'
175+
'SolidPhp\ValueObjects\Enum\is_public_nullary_factory_method'
163176
);
164177
foreach ($factoryMethods as $method) {
165178
$method->invoke(null);
@@ -169,7 +182,7 @@ function initialize_all_enum_instances(string $enumClass): void
169182
}
170183
}
171184

172-
function is_enum_factory_method(\ReflectionMethod $method): bool
185+
function is_public_nullary_factory_method(\ReflectionMethod $method): bool
173186
{
174187
return $method->isPublic()
175188
&& $method->isStatic()

‎test/Enum/Enum/EnumTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public function testInheritance(): void
4848
$this->assertNotSame(TestInheritanceParentEnum::PARENT(), TestInheritanceChildEnum::PARENT());
4949
$this->assertNotSame(TestInheritanceParentEnum::PARENT(), TestInheritanceChildEnum::PARENT());
5050
}
51+
52+
public function testDefineInstances(): void
53+
{
54+
$this->assertSame(TestCustomDefineInstances::instances(), [TestCustomDefineInstances::instance('FOO'), TestCustomDefineInstances::instance('BAR')]);
55+
}
5156
}
5257

5358
class TestInstancesEnum extends Enum
@@ -173,3 +178,15 @@ public function getChildProp()
173178
return $this->childProp;
174179
}
175180
}
181+
182+
final class TestCustomDefineInstances extends Enum
183+
{
184+
public const FOO = 'FOO';
185+
public const BAR = 'BAR';
186+
187+
protected static function defineInstances(): void
188+
{
189+
self::define(self::FOO);
190+
self::define(self::BAR);
191+
}
192+
}

‎test/Enum/EnumTrait/EnumTraitTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public function testInheritance(): void
4949
$this->assertNotSame(TestInheritanceParentEnum::PARENT(), TestInheritanceChildEnum::PARENT());
5050
$this->assertNotSame(TestInheritanceParentEnum::PARENT(), TestInheritanceChildEnum::PARENT());
5151
}
52+
53+
public function testDefineInstances(): void
54+
{
55+
$this->assertSame(TestCustomDefineInstances::instances(), [TestCustomDefineInstances::instance('FOO'), TestCustomDefineInstances::instance('BAR')]);
56+
}
5257
}
5358

5459
class TestInstancesEnum implements EnumInterface
@@ -185,3 +190,17 @@ public function getChildProp()
185190
return $this->childProp;
186191
}
187192
}
193+
194+
final class TestCustomDefineInstances
195+
{
196+
use EnumTrait;
197+
198+
public const FOO = 'FOO';
199+
public const BAR = 'BAR';
200+
201+
private static function defineInstances(): void
202+
{
203+
self::define(self::FOO);
204+
self::define(self::BAR);
205+
}
206+
}

0 commit comments

Comments
 (0)
Please sign in to comment.