Skip to content

Commit d4cde55

Browse files
authored
Merge pull request #1 from dbalabka/initial-implementation
Initial implementation
2 parents 5c31afa + 1922eb7 commit d4cde55

22 files changed

+860
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
composer.phar
2+
composer.lock
23
/vendor/
34

45
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM php:7.4-rc-cli
2+
RUN pecl install xdebug-2.8.0beta1

README.md

Lines changed: 202 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,202 @@
1-
# php-enumeration
2-
Implementation of enumeration classes in PHP. The better alternative for enums
1+
# PHP Enumeration classes
2+
Implementation of [Enumeration Classes](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types) in PHP.
3+
4+
In contrast to [existing solutions](#existing-solutions) this implementation avoid usage of [Magic methods](https://www.php.net/manual/en/language.oop5.magic.php) and
5+
[Reflection](https://www.php.net/manual/en/book.reflection.php) to provide better performance and code autocompletion.
6+
Also, we use static properties that can utilize the power of [Typed Properties](https://wiki.php.net/rfc/typed_properties_v2).
7+
The Enumeration Classes is much closer to other language implementations like [Java Enums](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)
8+
and [Python Enums](https://docs.python.org/3/library/enum.html).
9+
10+
11+
## Declaration
12+
13+
Basic way to declare named Enumeration class:
14+
```php
15+
<?php
16+
use Dbalabka\Enumeration;
17+
18+
final class Action extends Enumeration
19+
{
20+
public static $view;
21+
public static $edit;
22+
}
23+
// to avoid manual initialization you can setup "vladimmi/construct-static" custom loader
24+
Action::initialize();
25+
```
26+
27+
with Typed Properties support:
28+
```php
29+
<?php
30+
final class Day extends Enumeration
31+
{
32+
public static Day $sunday;
33+
public static Day $monday;
34+
public static Day $tuesday;
35+
public static Day $wednesday;
36+
public static Day $thursday;
37+
public static Day $friday;
38+
public static Day $saturday;
39+
}
40+
Day::initialize();
41+
```
42+
43+
By default enumeration class does not require the value to be provided. You can use constructor to set any types of values.
44+
1. Flag enum implementation example:
45+
```php
46+
<?php
47+
final class Flag extends Enumeration
48+
{
49+
public static Flag $ok;
50+
public static Flag $notOk;
51+
public static Flag $unavailable;
52+
53+
private int $flagValue;
54+
55+
protected function __construct()
56+
{
57+
$this->flagValue = 1 << $this->ordinal();
58+
}
59+
60+
public function getFlagValue() : int
61+
{
62+
return $this->flagValue;
63+
}
64+
}
65+
```
66+
2. You should override `initializeValues()` method to set custom values for each Enum element.
67+
```php
68+
<?php
69+
70+
final class Planet extends Enumeration
71+
{
72+
public static Planet $mercury;
73+
public static Planet $venus;
74+
public static Planet $earth;
75+
public static Planet $mars;
76+
public static Planet $jupiter;
77+
public static Planet $saturn;
78+
public static Planet $uranus;
79+
public static Planet $neptune;
80+
81+
private float $mass; // in kilograms
82+
private float $radius; // in meters
83+
84+
// universal gravitational constant (m3 kg-1 s-2)
85+
private const G = 6.67300E-11;
86+
87+
protected function __construct(float $mass, float $radius)
88+
{
89+
$this->mass = $mass;
90+
$this->radius = $radius;
91+
}
92+
93+
protected static function initializeValues() : void
94+
{
95+
self::$mercury = new self(3.303e+23, 2.4397e6);
96+
self::$venus = new self(4.869e+24, 6.0518e6);
97+
self::$earth = new self(5.976e+24, 6.37814e6);
98+
self::$mars = new self(6.421e+23, 3.3972e6);
99+
self::$jupiter = new self(1.9e+27, 7.1492e7);
100+
self::$saturn = new self(5.688e+26, 6.0268e7);
101+
self::$uranus = new self(8.686e+25, 2.5559e7);
102+
self::$neptune = new self(1.024e+26, 2.4746e7);
103+
}
104+
105+
public function surfaceGravity() : float
106+
{
107+
return self::G * $this->mass / ($this->radius * $this->radius);
108+
}
109+
110+
public function surfaceWeight(float $otherMass) {
111+
return $otherMass * $this->surfaceGravity();
112+
}
113+
}
114+
```
115+
116+
Declaration rules that developer should follow:
117+
1. You should always declare the Enum resulting enum class as `final`.
118+
> ...Allowing subclassing of enums that define members would lead to a violation of some important invariants of types and instances.
119+
> On the other hand, it makes sense to allow sharing some common behavior between a group of enumerations...
120+
> (from [Python Enum documentation](https://docs.python.org/3/library/enum.html#restricted-enum-subclassing))
121+
2. Constructor should always be declared as non-public (`private` or `protected`) to avoid unwanted class instantiation.
122+
3. Implementation is based on assumption that all class static properties are elements of Enum. If there is a need to declare
123+
any static property that isn't Enum element than you should override that `\Dbalabka\Enumeration::getStaticVars`.
124+
4. You must call `Dbalabka\Enumeration::initialize()` after each Enumeration class declaration or use
125+
[vladimmi/construct-static](https://github.com/vladimmi/construct-static) custom loader
126+
127+
## Usage
128+
```php
129+
<?php
130+
$viewAction = Action::$view;
131+
132+
// it is possible to compare Enum elements
133+
var_dump($viewAction === Action::$view);
134+
135+
// you can get Enum element by name
136+
$editAction = Action::valueOf('edit');
137+
138+
// iterate over all Enum elements
139+
foreach (Action::values() as $name => $action) {
140+
echo $action;
141+
}
142+
```
143+
144+
## Known issues
145+
### Readonly Properties
146+
In the current implementation, static property value might be occasionally replaced.
147+
[Readonly Properties](https://wiki.php.net/rfc/readonly_properties) is aimed to solve this issue.
148+
In ideal world Enum values should be declared as a constants. Unfortunately, it is not possible in PHP right now.
149+
```php
150+
<?php
151+
// It is possible but don't do it
152+
Action::$view = Action::$edit;
153+
// Following isn't possible in PHP 7.4 with declared properties types
154+
Action::$view = null;
155+
```
156+
157+
### Class static initialization
158+
Implementation rely on class static initialization which was proposed in [Static Class Constructor](https://wiki.php.net/rfc/static_class_constructor).
159+
RFC describes the possible workarounds. Simplest is to call initialization method right after class declaration,
160+
but it requires keep this in mind. Thanks to [Typed Properties](https://wiki.php.net/rfc/typed_properties_v2)
161+
we can control not initialized properties - PHP will throw and error in case of access to not initialized property.
162+
It might be automated with custom autoloader implemented in [vladimmi/construct-static](https://github.com/vladimmi/construct-static) library.
163+
```php
164+
<?php
165+
// You should always call initialize() method right after class declaration
166+
// To avoid manual initialization you can setup "vladimmi/construct-static" custom loader
167+
Action::initialize();
168+
```
169+
See [examples/class_static_construct.php](examples/class_static_construct.php) for example to overcome this limitation.
170+
171+
### Serialization
172+
There no possibility to serialize the singleton. As a result, we have to restrict direct Enum object serialization.
173+
[New custom object serialization mechanism](https://wiki.php.net/rfc/custom_object_serialization) does not help with direct Enum serialization
174+
but it give the possibility to control this in class which hold the reference to Enums instances. Also, it can be workaround
175+
with [Serializable Interface](https://www.php.net/manual/en/class.serializable.php) in similar way. So this problem somehow
176+
solves with worse developer experience. [TODO: clarify] Probably, [similar to Java Enums](https://stackoverflow.com/questions/15521309/is-custom-enum-serializable-too)
177+
the PHP Enums should not be serializable at all. The only way to serialize the Enum is to obtain the name of Enum constant
178+
and use valueOf() method to obtain the Enum constant.
179+
```php
180+
<?php
181+
// Following line will throw an exception
182+
serialize(Action::$view);
183+
```
184+
See [examples/serialization_php74.php](examples/serialization_php74.php) to overcome this limitation.
185+
It is possible to submit RFC to implement singleton serialization in PHP. For example [Java Enums](https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html)
186+
implements Serializable interface and replace class instance during unserialization in [readResolve()](https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html) method.
187+
188+
## Existing solutions
189+
In contrast to existing solutions and RFCs like
190+
* https://github.com/myclabs/php-enum
191+
* https://github.com/marc-mabe/php-enum
192+
* https://www.php.net/manual/en/class.splenum.php
193+
* [PHP RFC: Enumerated Types](https://wiki.php.net/rfc/enum)
194+
195+
(there a lot of [other PHP implementations](https://packagist.org/search/?query=php-enum))
196+
197+
## References
198+
* [Enum Types](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html) - The Java™ Tutorials
199+
* [Enum — Support for enumerations](https://docs.python.org/3/library/enum.html) - The Python Standard Library
200+
* [Class Enum<E extends Enum<E>>](https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html) - Java™ Platform, Standard Edition 7 API Specification
201+
* [Use enumeration classes instead of enum types](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types) - .NET microservices - Architecture e-book
202+
* [Enumeration Class implementation](https://github.com/dotnet-architecture/eShopOnContainers/blob/8960db40d43d79ad475799dedfe311ebc49cab99/src/Services/Ordering/Ordering.Domain/SeedWork/Enumeration.cs) - Microservices Architecture and Containers based Reference Application

composer.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "dbalabka/php-enumeration",
3+
"description": "Implementation of enumeration classes in PHP",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Dmitry Balabka",
9+
"email": "dmitry.balabka@gmail.com"
10+
}
11+
],
12+
"repositories": [{"type": "vcs", "url": "https://github.com/dbalabka/construct-static.git"}],
13+
"require": {
14+
"php": ">=7.1"
15+
},
16+
"require-dev": {
17+
"vladimmi/construct-static": "dev-master",
18+
"myclabs/php-enum": "^1.0",
19+
"phpunit/phpunit": "^8.3",
20+
"phpbench/phpbench": "^0.16.9"
21+
},
22+
"suggest": {
23+
"vladimmi/construct-static": "Allows to call __constructStatic on class load"
24+
},
25+
"autoload": {
26+
"psr-4": {
27+
"Dbalabka\\": "src"
28+
}
29+
},
30+
"autoload-dev": {
31+
"psr-4": {
32+
"Dbalabka\\Examples\\": "examples",
33+
"Dbalabka\\Tests\\": "tests"
34+
}
35+
}
36+
}

examples/Fixtures/Action.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Examples\Fixtures;
5+
6+
use Dbalabka\Enumeration;
7+
8+
final class Action extends Enumeration
9+
{
10+
public static Action $view;
11+
public static Action $edit;
12+
}

examples/Fixtures/Color.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Examples\Fixtures;
5+
6+
use Dbalabka\Enumeration;
7+
8+
final class Color extends Enumeration
9+
{
10+
public static $red;
11+
public static $green;
12+
public static $blue;
13+
14+
private $value;
15+
16+
public function __construct(int $value)
17+
{
18+
$this->value = $value;
19+
}
20+
21+
protected static function initializeValues(): void
22+
{
23+
self::$red = new self(1);
24+
self::$blue = new self(2);
25+
self::$green = new self(3);
26+
}
27+
}

examples/Fixtures/Day.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Examples\Fixtures;
5+
6+
use Dbalabka\Enumeration;
7+
8+
final class Day extends Enumeration
9+
{
10+
public static Day $sunday;
11+
public static Day $monday;
12+
public static Day $tuesday;
13+
public static Day $wednesday;
14+
public static Day $thursday;
15+
public static Day $friday;
16+
public static Day $saturday;
17+
}

examples/Fixtures/Planet.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Dbalabka\Examples\Fixtures;
5+
6+
use Dbalabka\Enumeration;
7+
8+
final class Planet extends Enumeration
9+
{
10+
public static Planet $mercury;
11+
public static Planet $venus;
12+
public static Planet $earth;
13+
public static Planet $mars;
14+
public static Planet $jupiter;
15+
public static Planet $saturn;
16+
public static Planet $uranus;
17+
public static Planet $neptune;
18+
19+
private float $mass; // in kilograms
20+
private float $radius; // in meters
21+
22+
// universal gravitational constant (m3 kg-1 s-2)
23+
private const G = 6.67300E-11;
24+
25+
protected function __construct(float $mass, float $radius)
26+
{
27+
$this->mass = $mass;
28+
$this->radius = $radius;
29+
}
30+
31+
protected static function initializeValues() : void
32+
{
33+
self::$mercury = new self(3.303e+23, 2.4397e6);
34+
self::$venus = new self(4.869e+24, 6.0518e6);
35+
self::$earth = new self(5.976e+24, 6.37814e6);
36+
self::$mars = new self(6.421e+23, 3.3972e6);
37+
self::$jupiter = new self(1.9e+27, 7.1492e7);
38+
self::$saturn = new self(5.688e+26, 6.0268e7);
39+
self::$uranus = new self(8.686e+25, 2.5559e7);
40+
self::$neptune = new self(1.024e+26, 2.4746e7);
41+
}
42+
43+
public function surfaceGravity() : float
44+
{
45+
return self::G * $this->mass / ($this->radius * $this->radius);
46+
}
47+
48+
public function surfaceWeight(float $otherMass) {
49+
return $otherMass * $this->surfaceGravity();
50+
}
51+
}

0 commit comments

Comments
 (0)