diff --git a/composer.json b/composer.json
index 2aa293c..63b8f16 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "intraworlds/enum",
- "description": "Lightweight implementation of enum value object in PHP.",
+ "description": "Lightweight implementation of enum value object",
"type": "library",
"require-dev": {
"phpunit/phpunit": "^7.4"
@@ -12,6 +12,11 @@
"email": "ondrej.esler@intraworlds.com"
}
],
+ "autoload": {
+ "psr-4": {
+ "IW\\": "src"
+ }
+ },
"require": {
"php": ">=7.1"
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..24927f4
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,8 @@
+
+
+
+
+ tests/
+
+
+
diff --git a/src/Enum.php b/src/Enum.php
new file mode 100644
index 0000000..e91d760
--- /dev/null
+++ b/src/Enum.php
@@ -0,0 +1,155 @@
+ true
+ *
+ * // ALWAYS use strict equal! see the problem
+ * var_dump($a == Hash::SHA1());
+ * > true
+ *
+ * var_dump($a->getValue() === Hash::MD5);
+ * > true
+ *
+ * function crack(Hash $hash) {
+ * echo 'cracking ... ' . $hash;
+ * }
+ *
+ * crack(Hash::SHA1());
+ * > cracking ... sha1
+ * crack(Hash::SHA1);
+ * > throws TypeError
+ *
+ * switch ($a->getValue()) {
+ * case Hash::MD5: ...
+ * case Hash::SHA1: ...
+ * }
+ *
+ * @author Ondrej Esler
+ */
+abstract class Enum
+{
+ /** @var string */
+ private $key;
+
+ /** @var mixed */
+ private $_value;
+
+ /** @var array */
+ private static $_singletons = [];
+
+ /**
+ * Constructor
+ *
+ * @param string $key
+ */
+ final private function __construct(string $key) {
+ if (!defined(static::class . '::' . $key)) {
+ throw new \InvalidArgumentException('Unknown constant: ' . static::class . '::' . $key);
+ }
+
+ $this->key = $key;
+ $this->value = \constant(static::class . '::' . $key);
+ }
+
+ /**
+ * Map class constants to static methods const FOO => FOO(), returns singleton of an enum with value of constant
+ *
+ * @param string $key
+ * @param array $arguments
+ *
+ * @return Enum
+ */
+ final public static function __callStatic(string $key, array $arguments): Enum {
+ $singletonId = static::class . $key;
+
+ if (empty(self::$_singletons[$singletonId])) {
+ self::$_singletons[$singletonId] = new static($key);
+ }
+
+ return self::$_singletons[$singletonId];
+ }
+
+ /**
+ * Returns string representation of constant value
+ *
+ * @return string
+ */
+ final public function __toString(): string {
+ return is_array($this->value) ? json_encode($this->value) : (string) $this->value;
+ }
+
+ /**
+ * Return TRUE if given enum is the same, FALSE otherwise
+ *
+ * @param Enum $enum
+ *
+ * @return bool
+ */
+ final public function equals(Enum $enum): bool {
+ return $this === $enum;
+ }
+
+ /**
+ * Returns name of constant
+ *
+ * @return string
+ */
+ final public function getKey(): string {
+ return $this->key;
+ }
+
+ /**
+ * Returns value of constant
+ *
+ * @return mixed
+ */
+ final public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Returns list of names of constants
+ *
+ * @return string[]
+ */
+ final public static function keys(): array {
+ return array_keys(static::toArray());
+ }
+
+ /**
+ * Returns list of values of constants
+ *
+ * @return mixed[]
+ */
+ final public static function values(): array {
+ return array_values(static::toArray());
+ }
+
+ /**
+ * Returns array with all constants eg. [key => value, ...]
+ *
+ * @return mixed[]
+ */
+ final public static function toArray(): array {
+ return (new \ReflectionClass(static::class))->getConstants();
+ }
+
+}
diff --git a/tests/EnumTest.php b/tests/EnumTest.php
new file mode 100644
index 0000000..7938531
--- /dev/null
+++ b/tests/EnumTest.php
@@ -0,0 +1,79 @@
+expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unknown constant: IW\IntraWorldsEnum::KIEV');
+ IntraWorldsEnum::KIEV();
+ }
+
+ function testMethodEquals() {
+ $city = IntraWorldsEnum::PILSEN();
+
+ $this->assertTrue($city->equals(IntraWorldsEnum::PILSEN()));
+ $this->assertTrue(IntraWorldsEnum::PILSEN()->equals($city));
+
+ $this->assertFalse($city->equals(IntraWorldsEnum::NEW_YORK()));
+ $this->assertFalse(IntraWorldsEnum::TAMPA()->equals($city));
+ }
+
+ function testThatTwoInstancesAreTheSame() {
+ $this->assertSame(IntraWorldsEnum::TAMPA(), IntraWorldsEnum::TAMPA());
+ }
+
+ function testMethodToString() {
+ $this->assertSame('1', (string) IntraWorldsEnum::PILSEN());
+ $this->assertSame('0.33333333333333', (string) IntraWorldsEnum::TAMPA());
+ $this->assertSame('{"foo":["bar"]}', (string) IntraWorldsEnum::MUNICH());
+ }
+
+ function testMethodGetValue() {
+ $this->assertSame(true, IntraWorldsEnum::PILSEN()->getValue());
+ $this->assertSame(['foo' => ['bar']], IntraWorldsEnum::MUNICH()->getValue());
+ $this->assertSame(42, IntraWorldsEnum::NEW_YORK()->getValue());
+ $this->assertSame(1 / 3, IntraWorldsEnum::TAMPA()->getValue());
+ }
+
+ function testMethodGetKey() {
+ $this->assertSame('PILSEN', IntraWorldsEnum::PILSEN()->getKey());
+ $this->assertSame('MUNICH', IntraWorldsEnum::MUNICH()->getKey());
+ $this->assertSame('NEW_YORK', IntraWorldsEnum::NEW_YORK()->getKey());
+ $this->assertSame('TAMPA', IntraWorldsEnum::TAMPA()->getKey());
+ }
+
+ function testMethodKeys() {
+ $this->assertSame(['PILSEN', 'MUNICH', 'NEW_YORK', 'TAMPA'], IntraWorldsEnum::keys());
+ }
+
+ function testMethodValues() {
+ $this->assertSame([true, ['foo' => ['bar']], 42, 1 / 3], IntraWorldsEnum::values());
+ }
+
+ function testMethodToArray() {
+ $this->assertSame([
+ 'PILSEN' => true,
+ 'MUNICH' => ['foo' => ['bar']],
+ 'NEW_YORK' => 42,
+ 'TAMPA' => 1 / 3,
+ ], IntraWorldsEnum::toArray());
+ }
+}
+
+/**
+ * A value object representing testing enum
+ *
+ * @author Ondrej Esler
+ */
+class IntraWorldsEnum extends Enum
+{
+ const PILSEN = true;
+ const MUNICH = ['foo' => ['bar']];
+ const NEW_YORK = 42;
+ const TAMPA = 1 / 3;
+}