Skip to content

Commit

Permalink
implementation and test
Browse files Browse the repository at this point in the history
  • Loading branch information
Ondřej Ešler committed Nov 7, 2018
1 parent 8f756e2 commit dbf969a
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 1 deletion.
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -12,6 +12,11 @@
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"IW\\": "src"
}
},
"require": {
"php": ">=7.1"
}
Expand Down
8 changes: 8 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<testsuites>
<testsuite>
<directory>tests/</directory>
</testsuite>
</testsuites>
</phpunit>
155 changes: 155 additions & 0 deletions src/Enum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php declare(strict_types=1);

namespace IW;

/**
* Base for enum-like value object, it defines static methods with same names as constants which returns singleton
* of particular enum and its value.
*
* Implementation is simpler version of library https://packagist.org/packages/myclabs/php-enum with added singletons
* to be able use strict equals.
*
* Examples:
*
* final class Hash extends Enum
* {
* const MD5 = 'md5';
* const SHA1 = 'sha1';
* }
*
* $a = Hash::MD5();
* var_dump($a === Hash::MD5());
* > 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 <[email protected]>
*/
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();
}

}
79 changes: 79 additions & 0 deletions tests/EnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php declare(strict_types=1);

namespace IW;

use PHPUnit\Framework\TestCase;

class EnumTest extends TestCase
{

function testFailWhenInvalidKey() {
$this->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 <[email protected]>
*/
class IntraWorldsEnum extends Enum
{
const PILSEN = true;
const MUNICH = ['foo' => ['bar']];
const NEW_YORK = 42;
const TAMPA = 1 / 3;
}

0 comments on commit dbf969a

Please sign in to comment.