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; +}