Skip to content

Commit 839e85e

Browse files
authored
Implement ideas from RFC (#1)
* On create object with invalid value throws ValueError instead UnexpectedValueException * Use private constants in enum object * Rename methods `toObjects()` to `cases()` and `toValues()` to `values()` * Remove immutability * Add method `getName()` * Add method `tryFrom()` * Add protected method `match()` * Add info to change log * Fix readme
1 parent 9cf6a45 commit 839e85e

File tree

6 files changed

+220
-96
lines changed

6 files changed

+220
-96
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# PHP Enum Implementation Change Log
22

3+
## 4.0.0 May 27, 2021
4+
5+
Implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations):
6+
7+
- New: Add protected method `match()`.
8+
- New: Add factory method `tryFrom()`.
9+
- New: Add method `getName()`.
10+
- Chg: Remove immutability objects.
11+
- Chg: Rename methods `toObjects()` to `cases()` and `toValues()` to `values()`.
12+
- Chg: Use private constants in enum object.
13+
- Chg: On create object via method `from()` with invalid value throws `ValueError` instead `UnexpectedValueException`.
14+
315
## 3.0.0 May 26, 2021
416

517
- Chg: Rewrite the package from scratch.

README.md

+52-17
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fvjik%2Fphp-enum%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/vjik/php-enum/master)
77
[![static analysis](https://github.com/vjik/php-enum/workflows/static%20analysis/badge.svg)](https://github.com/vjik/php-enum/actions?query=workflow%3A%22static+analysis%22)
88

9-
The package provide abstract class `Enum` that intended to create
10-
[enumerated objects](https://en.wikipedia.org/wiki/Enumerated_type) with support [extra data](#extradata) and
11-
auxiliary static functions [`toValues()`](#toList), [`toObjects()`](#toObjects) and [`isValid()`](#isValid).
9+
The package implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations) and provide abstract class `Enum` that intended to create
10+
[enumerated objects](https://en.wikipedia.org/wiki/Enumerated_type) with support [extra data](#extradata) and auxiliary static functions [`values()`](#values), [`cases()`](#cases) and [`isValid()`](#isValid).
1211

1312
## Requirements
1413

@@ -36,9 +35,9 @@ use Vjik\Enum\Enum;
3635
*/
3736
final class Status extends Enum
3837
{
39-
public const NEW = 'new';
40-
public const PROCESS = 'process';
41-
public const DONE = 'done';
38+
private const NEW = 'new';
39+
private const PROCESS = 'process';
40+
private const DONE = 'done';
4241
}
4342
```
4443

@@ -50,6 +49,17 @@ final class Status extends Enum
5049
$process = Status::from('process');
5150
```
5251

52+
On create object with invalid value throws `ValueError`.
53+
54+
#### By static method `tryFrom()`
55+
56+
```php
57+
$process = Status::tryFrom('process'); // Status object with value "process"
58+
$process = Status::tryFrom('not-exists'); // null
59+
```
60+
61+
On create object with invalid value returns `null`.
62+
5363
#### By static method with a name identical to the constant name
5464

5565
Static methods are automatically implemented to provide quick access to an enum value.
@@ -58,9 +68,17 @@ Static methods are automatically implemented to provide quick access to an enum
5868
$process = Status::PROCESS();
5969
```
6070

71+
### Getting value and name
72+
73+
```php
74+
Status::DONE()->getName(); // DONE
75+
Status::DONE()->getValue(); // done
76+
```
77+
6178
### <a name="extradata"></a>Class with extra data
6279

63-
Set data in the protected static function `data()` and create getters using the protected method `getPropertyValue()`:
80+
Set data in the protected static function `data()` and create getters using the protected method `getPropertyValue()`.
81+
Also you can create getter using protected method `match()`.
6482

6583
```php
6684
use Vjik\Enum\Enum;
@@ -71,8 +89,8 @@ use Vjik\Enum\Enum;
7189
*/
7290
final class Action extends Enum
7391
{
74-
public const CREATE = 1;
75-
public const UPDATE = 2;
92+
private const CREATE = 1;
93+
private const UPDATE = 2;
7694

7795
protected static function data(): array
7896
{
@@ -91,33 +109,50 @@ final class Action extends Enum
91109
/** @var string */
92110
return $this->getPropertyValue('tip');
93111
}
112+
113+
public function getColor(): string
114+
{
115+
return $this->match([
116+
self::CREATE => 'red',
117+
self::UPDATE => 'blue',
118+
]);
119+
}
120+
121+
public function getCode(): int
122+
{
123+
return $this->match([
124+
self::CREATE => 1,
125+
], 99);
126+
}
94127
}
95128
```
96129

97130
Usage:
98131

99132
```php
100133
echo Action::CREATE()->getTip();
134+
echo Action::CREATE()->getColor();
135+
echo Action::CREATE()->getCode();
101136
```
102137

103138
### Auxiliary static functions
104139

105-
#### <a name="toValues"></a> List of values `toValues()`
140+
#### <a name="values"></a> List of values `values()`
106141

107-
Returns array of pairs constant names and values.
142+
Returns list of values.
108143

109144
```php
110-
// ['CREATE' => 1, 'UPDATE' => 2]
111-
Action::toValues();
145+
// [1, 2]
146+
Action::values();
112147
```
113148

114-
#### <a name="toObjects"></a> List of objects `toObjects()`
149+
#### <a name="cases"></a> List of objects `cases()`
115150

116-
Returns array of pairs constant names and objects:
151+
Returns list of objects:
117152

118153
```php
119-
// ['CREATE' => $createObject, 'UPDATE' => $updateObject]
120-
Action::toObjects();
154+
// [$createObject, $updateObject]
155+
Action::cases();
121156
```
122157

123158
#### <a name="isValid"></a> Validate value `isValid()`

src/Enum.php

+78-33
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
use BadMethodCallException;
88
use ReflectionClass;
99
use ReflectionClassConstant;
10-
use UnexpectedValueException;
10+
use ValueError;
1111

12-
use function constant;
13-
use function defined;
12+
use function array_key_exists;
1413
use function in_array;
1514

1615
abstract class Enum
1716
{
17+
private string $name;
1818
private mixed $value;
1919

2020
/**
@@ -27,15 +27,17 @@ abstract class Enum
2727
*/
2828
private static array $instances = [];
2929

30-
final protected function __construct(mixed $value)
30+
final protected function __construct(string $name, mixed $value)
3131
{
32-
if (!self::isValid($value)) {
33-
throw new UnexpectedValueException("Value '$value' is not part of the enum " . static::class . '.');
34-
}
35-
32+
$this->name = $name;
3633
$this->value = $value;
3734
}
3835

36+
final public function getName(): string
37+
{
38+
return $this->name;
39+
}
40+
3941
final public function getValue(): mixed
4042
{
4143
return $this->value;
@@ -51,7 +53,20 @@ final public function __toString(): string
5153
*/
5254
final public static function from(mixed $value): self
5355
{
54-
return new static($value);
56+
$object = static::getInstanceByValue($value);
57+
if ($object === null) {
58+
throw new ValueError("Value '$value' is not part of the enum " . static::class . '.');
59+
}
60+
61+
return $object;
62+
}
63+
64+
/**
65+
* @return static|self
66+
*/
67+
final public static function tryFrom(mixed $value): ?self
68+
{
69+
return static::getInstanceByValue($value);
5570
}
5671

5772
/**
@@ -61,54 +76,43 @@ final public static function __callStatic(string $name, array $arguments): self
6176
{
6277
$class = static::class;
6378
if (!isset(self::$instances[$class][$name])) {
64-
$constant = $class . '::' . $name;
65-
if (!defined($constant)) {
79+
$enumValues = static::getEnumValues();
80+
if (!array_key_exists($name, $enumValues)) {
6681
$message = "No static method or enum constant '$name' in class " . static::class . '.';
6782
throw new BadMethodCallException($message);
6883
}
69-
return self::$instances[$class][$name] = new static(constant($constant));
84+
self::$instances[$class][$name] = new static($name, $enumValues[$name]);
7085
}
71-
return clone self::$instances[$class][$name];
86+
return self::$instances[$class][$name];
7287
}
7388

74-
final public static function toValues(): array
89+
final public static function values(): array
7590
{
76-
$class = static::class;
77-
78-
if (!isset(static::$cache[$class])) {
79-
/** @psalm-suppress TooManyArguments Remove this after fix https://github.com/vimeo/psalm/issues/5837 */
80-
static::$cache[$class] = (new ReflectionClass($class))->getConstants(ReflectionClassConstant::IS_PUBLIC);
81-
}
82-
83-
return static::$cache[$class];
91+
return array_values(self::getEnumValues());
8492
}
8593

8694
/**
8795
* @return static[]
8896
*/
89-
final public static function toObjects(): array
97+
final public static function cases(): array
9098
{
9199
$class = static::class;
92100

93101
$objects = [];
94-
/**
95-
* @var string $key
96-
* @var mixed $value
97-
*/
98-
foreach (self::toValues() as $key => $value) {
99-
if (isset(self::$instances[$class][$key])) {
100-
$objects[$key] = clone self::$instances[$class][$key];
101-
} else {
102-
$objects[$key] = self::$instances[$class][$key] = new static($value);
102+
/** @var mixed $value */
103+
foreach (self::getEnumValues() as $key => $value) {
104+
if (!isset(self::$instances[$class][$key])) {
105+
self::$instances[$class][$key] = new static($key, $value);
103106
}
107+
$objects[] = self::$instances[$class][$key];
104108
}
105109

106110
return $objects;
107111
}
108112

109113
final public static function isValid(mixed $value): bool
110114
{
111-
return in_array($value, static::toValues(), true);
115+
return in_array($value, static::getEnumValues(), true);
112116
}
113117

114118
/**
@@ -124,4 +128,45 @@ final protected function getPropertyValue(string $key, mixed $default = null): m
124128
/** @psalm-suppress MixedArrayOffset */
125129
return static::data()[$this->value][$key] ?? $default;
126130
}
131+
132+
final protected function match(array $data, mixed $default = null): mixed
133+
{
134+
/** @psalm-suppress MixedArrayOffset */
135+
return $data[$this->value] ?? $default;
136+
}
137+
138+
/**
139+
* @return static|null
140+
*/
141+
private static function getInstanceByValue(mixed $value): ?self
142+
{
143+
$class = static::class;
144+
145+
/** @var mixed $enumValue */
146+
foreach (self::getEnumValues() as $key => $enumValue) {
147+
if ($enumValue === $value) {
148+
if (!isset(self::$instances[$class][$key])) {
149+
self::$instances[$class][$key] = new static($key, $value);
150+
}
151+
return self::$instances[$class][$key];
152+
}
153+
}
154+
155+
return null;
156+
}
157+
158+
/**
159+
* @psalm-return array<string,mixed>
160+
*/
161+
private static function getEnumValues(): array
162+
{
163+
$class = static::class;
164+
165+
if (!isset(static::$cache[$class])) {
166+
/** @psalm-suppress TooManyArguments Remove this after fix https://github.com/vimeo/psalm/issues/5837 */
167+
static::$cache[$class] = (new ReflectionClass($class))->getConstants(ReflectionClassConstant::IS_PRIVATE);
168+
}
169+
170+
return static::$cache[$class];
171+
}
127172
}

0 commit comments

Comments
 (0)