diff --git a/README.md b/README.md index d0c3001..95cfbe9 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,6 @@ class Action extends Enum ## Usage ```php -$action = new Action(Action::VIEW); - -// or $action = Action::VIEW(); ``` @@ -58,9 +55,22 @@ function setAction(Action $action) { } ``` +Each Enum instance for a given key is a singleton, so you can use: + +```php +function setAction(Action $action) { + if ($action === Action::VIEW()) { + // + } +} +``` + +**Note** that this is not true, if you `unserialize()` Enums. +In case another Enum instance already exists, +an `E_USER_NOTICE` is triggered. + ## Documentation -- `__construct()` The constructor checks that the value exist in the enum - `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) - `getValue()` Returns the current value of the enum - `getKey()` Returns the key of the current value on Enum @@ -74,6 +84,8 @@ Static methods: - `isValid()` Check if tested value is valid on enum set - `isValidKey()` Check if tested key is valid on enum set - `search()` Return key for searched value +- `fromKey()` Return Enum instance for the given key +- `fromValue()` Return Enum instance for the given value ### Static methods @@ -91,23 +103,8 @@ $action = Action::EDIT(); Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). -If you care about IDE autocompletion, you can either implement the static methods yourself: - -```php -class Action extends Enum -{ - const VIEW = 'view'; - - /** - * @return Action - */ - public static function VIEW() { - return new Action(self::VIEW); - } -} -``` - -or you can use phpdoc (this is supported in PhpStorm for example): +If you care about IDE autocompletion, +you can use phpdoc (this is supported in PhpStorm for example): ```php /** diff --git a/src/Enum.php b/src/Enum.php index 140e722..eee1b6c 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -6,6 +6,8 @@ namespace MyCLabs\Enum; +use BadMethodCallException; + /** * Base Enum class * @@ -18,32 +20,27 @@ abstract class Enum { /** - * Enum value + * Enum name * - * @var mixed + * @var string */ - protected $value; + private $name; /** - * Store existing constants in a static cache per object. + * Enum value * - * @var array + * @var mixed */ - protected static $cache = array(); + private $value; /** * Creates a new value of some type * * @param mixed $value - * - * @throws \UnexpectedValueException if incompatible type is given. */ - public function __construct($value) + final private function __construct($name, $value) { - if (!$this->isValid($value)) { - throw new \UnexpectedValueException("Value '$value' is not part of the enum " . get_called_class()); - } - + $this->name = $name; $this->value = $value; } @@ -62,7 +59,7 @@ public function getValue() */ public function getKey() { - return static::search($this->value); + return $this->name; } /** @@ -74,15 +71,14 @@ public function __toString() } /** - * Compares one Enum with another. - * - * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 - * - * @return bool True if Enums are equal, false if not equal + * Register object in cache and trigger a notice if it already exists. */ - final public function equals(Enum $enum) + public function __wakeup() { - return $this->getValue() === $enum->getValue() && get_called_class() == get_class($enum); + $enum = EnumManager::get($this); + if ($enum !== $this) { + trigger_error("Enum is already initialized", E_USER_NOTICE); + } } /** @@ -104,8 +100,8 @@ public static function values() { $values = array(); - foreach (static::toArray() as $key => $value) { - $values[$key] = new static($value); + foreach (static::toArray() as $name => $value) { + $values[$name] = EnumManager::get(new static($name, $value)); } return $values; @@ -118,13 +114,7 @@ public static function values() */ public static function toArray() { - $class = get_called_class(); - if (!array_key_exists($class, static::$cache)) { - $reflection = new \ReflectionClass($class); - static::$cache[$class] = $reflection->getConstants(); - } - - return static::$cache[$class]; + return EnumManager::constants(new static(null, null)); } /** @@ -165,6 +155,36 @@ public static function search($value) return array_search($value, static::toArray(), true); } + /** + * Returns Enum by value + * + * @return static + */ + public static function fromValue($value) + { + $name = static::search($value); + if ($name === false) { + return null; + } + + return EnumManager::get(new static($name, $value)); + } + + /** + * Returns Enum by key + * + * @return static + */ + public static function fromKey($name) + { + $array = static::toArray(); + if (isset($array[$name]) || array_key_exists($name, $array)) { + return EnumManager::get(new static($name, $array[$name])); + } + + return null; + } + /** * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant * @@ -172,15 +192,17 @@ public static function search($value) * @param array $arguments * * @return static - * @throws \BadMethodCallException + * @throws BadMethodCallException */ public static function __callStatic($name, $arguments) { - $array = static::toArray(); - if (isset($array[$name])) { - return new static($array[$name]); + $result = static::fromKey($name); + + if ($result === null) { + $msg = "No static method or enum constant '$name' in class " . get_called_class(); + throw new BadMethodCallException($msg); } - throw new \BadMethodCallException("No static method or enum constant '$name' in class " . get_called_class()); + return $result; } } diff --git a/src/EnumManager.php b/src/EnumManager.php new file mode 100644 index 0000000..e61065c --- /dev/null +++ b/src/EnumManager.php @@ -0,0 +1,55 @@ +getName(); + $name = $enum->getKey(); + + if (isset(self::$instances[$class][$name])) { + return self::$instances[$class][$name]; + } + + self::$instances[$class][$name] = $enum; + return $enum; + } + + /** + * Returns all possible values as an array + * + * @return array Constant name in key, constant value in value + */ + public static function constants(Enum $enum) + { + $reflection = new ReflectionObject($enum); + $result = $reflection->getConstants(); + return $result; + } +} diff --git a/tests/EnumTest.php b/tests/EnumTest.php index c468017..396ec02 100644 --- a/tests/EnumTest.php +++ b/tests/EnumTest.php @@ -18,13 +18,13 @@ class EnumTest extends \PHPUnit\Framework\TestCase */ public function testGetValue() { - $value = new EnumFixture(EnumFixture::FOO); + $value = EnumFixture::FOO(); $this->assertEquals(EnumFixture::FOO, $value->getValue()); - $value = new EnumFixture(EnumFixture::BAR); + $value = EnumFixture::BAR(); $this->assertEquals(EnumFixture::BAR, $value->getValue()); - $value = new EnumFixture(EnumFixture::NUMBER); + $value = EnumFixture::NUMBER(); $this->assertEquals(EnumFixture::NUMBER, $value->getValue()); } @@ -33,21 +33,11 @@ public function testGetValue() */ public function testGetKey() { - $value = new EnumFixture(EnumFixture::FOO); + $value = EnumFixture::FOO(); $this->assertEquals('FOO', $value->getKey()); $this->assertNotEquals('BA', $value->getKey()); } - /** - * @dataProvider invalidValueProvider - * @expectedException UnexpectedValueException - * @expectedExceptionMessage is not part of the enum MyCLabs\Tests\Enum\EnumFixture - */ - public function testCreatingEnumWithInvalidValue($value) - { - new EnumFixture($value); - } - /** * Contains values not existing in EnumFixture * @return array @@ -70,9 +60,9 @@ public function testToString($expected, $enumObject) public function toStringProvider() { return array( - array(EnumFixture::FOO, new EnumFixture(EnumFixture::FOO)), - array(EnumFixture::BAR, new EnumFixture(EnumFixture::BAR)), - array((string) EnumFixture::NUMBER, new EnumFixture(EnumFixture::NUMBER)), + array(EnumFixture::FOO, EnumFixture::FOO()), + array(EnumFixture::BAR, EnumFixture::BAR()), + array((string) EnumFixture::NUMBER, EnumFixture::NUMBER()), ); } @@ -102,13 +92,13 @@ public function testValues() { $values = EnumFixture::values(); $expectedValues = array( - "FOO" => new EnumFixture(EnumFixture::FOO), - "BAR" => new EnumFixture(EnumFixture::BAR), - "NUMBER" => new EnumFixture(EnumFixture::NUMBER), - "PROBLEMATIC_NUMBER" => new EnumFixture(EnumFixture::PROBLEMATIC_NUMBER), - "PROBLEMATIC_NULL" => new EnumFixture(EnumFixture::PROBLEMATIC_NULL), - "PROBLEMATIC_EMPTY_STRING" => new EnumFixture(EnumFixture::PROBLEMATIC_EMPTY_STRING), - "PROBLEMATIC_BOOLEAN_FALSE" => new EnumFixture(EnumFixture::PROBLEMATIC_BOOLEAN_FALSE), + "FOO" => EnumFixture::FOO(), + "BAR" => EnumFixture::BAR(), + "NUMBER" => EnumFixture::NUMBER(), + "PROBLEMATIC_NUMBER" => EnumFixture::PROBLEMATIC_NUMBER(), + "PROBLEMATIC_NULL" => EnumFixture::PROBLEMATIC_NULL(), + "PROBLEMATIC_EMPTY_STRING" => EnumFixture::PROBLEMATIC_EMPTY_STRING(), + "PROBLEMATIC_BOOLEAN_FALSE" => EnumFixture::PROBLEMATIC_BOOLEAN_FALSE(), ); $this->assertEquals($expectedValues, $values); @@ -138,9 +128,9 @@ public function testToArray() */ public function testStaticAccess() { - $this->assertEquals(new EnumFixture(EnumFixture::FOO), EnumFixture::FOO()); - $this->assertEquals(new EnumFixture(EnumFixture::BAR), EnumFixture::BAR()); - $this->assertEquals(new EnumFixture(EnumFixture::NUMBER), EnumFixture::NUMBER()); + $this->assertEquals(EnumFixture::FOO(), EnumFixture::FOO()); + $this->assertEquals(EnumFixture::BAR(), EnumFixture::BAR()); + $this->assertEquals(EnumFixture::NUMBER(), EnumFixture::NUMBER()); } /** @@ -212,39 +202,71 @@ public function searchProvider() { } /** - * equals() + * __callStatic() */ - public function testEquals() + public function testSameInstance() { - $foo = new EnumFixture(EnumFixture::FOO); - $number = new EnumFixture(EnumFixture::NUMBER); - $anotherFoo = new EnumFixture(EnumFixture::FOO); + $foo1 = EnumFixture::FOO(); + $foo2 = EnumFixture::FOO(); - $this->assertTrue($foo->equals($foo)); - $this->assertFalse($foo->equals($number)); - $this->assertTrue($foo->equals($anotherFoo)); + $this->assertSame($foo1, $foo2); } /** - * equals() + * fromKey() */ - public function testEqualsComparesProblematicValuesProperly() + public function testFromKey() { - $false = new EnumFixture(EnumFixture::PROBLEMATIC_BOOLEAN_FALSE); - $emptyString = new EnumFixture(EnumFixture::PROBLEMATIC_EMPTY_STRING); - $null = new EnumFixture(EnumFixture::PROBLEMATIC_NULL); + $number = EnumFixture::BAR(); + $fromValue = EnumFixture::fromKey('BAR'); - $this->assertTrue($false->equals($false)); - $this->assertFalse($false->equals($emptyString)); - $this->assertFalse($emptyString->equals($null)); - $this->assertFalse($null->equals($false)); + $this->assertSame($number, $fromValue); } /** - * equals() + * fromValue() */ - public function testEqualsConflictValues() + public function testFromValue() { - $this->assertFalse(EnumFixture::FOO()->equals(EnumConflict::FOO())); + $enum = EnumFixture::NUMBER(); + $number = EnumFixture::fromValue(42); + $inexistant = EnumFixture::fromValue('inexistant'); + + $this->assertSame($enum, $number); + $this->assertSame(null, $inexistant); + } + + /** + * __wakeup() + */ + public function testUnserialize() + { + $ser = 'O:37:"MyCLabs\Tests\Enum\UnserializeFixture":2:{' + . 's:23:"#MyCLabs\Enum\Enum#name";s:4:"ONCE";' + . 's:24:"#MyCLabs\Enum\Enum#value";s:2:"OK";}'; + $once = unserialize(strtr($ser, "#", "\0")); + + $this->assertSame($once, UnserializeFixture::ONCE()); + } + + /** + * __wakeup() + */ + public function testUnserializeError() + { + $triggered = false; + set_error_handler(function () use (&$triggered) { + $triggered = true; + return true; + }, E_USER_NOTICE); + + $ser = 'O:30:"MyCLabs\Tests\Enum\EnumFixture":2:{' + . 's:23:"#MyCLabs\Enum\Enum#name";s:3:"FOO";' + . 's:24:"#MyCLabs\Enum\Enum#value";s:3:"foo";}'; + $foo = unserialize(strtr($ser, "#", "\0")); + + restore_error_handler(); + + $this->assertTrue($triggered); } } diff --git a/tests/UnserializeFixture.php b/tests/UnserializeFixture.php new file mode 100644 index 0000000..473f148 --- /dev/null +++ b/tests/UnserializeFixture.php @@ -0,0 +1,19 @@ +