diff --git a/README.md b/README.md index 472f10ab..55320e91 100644 --- a/README.md +++ b/README.md @@ -42,15 +42,13 @@ class UserStatus extends Enum const ACTIVE = 'a'; const DELETED = 'd'; - // all scalar datatypes are supported + // all scalar data types and arrays are supported as enumerator values const NIL = null; const BOOLEAN = true; const INT = 1234; const STR = 'string'; const FLOAT = 0.123; - - // Arrays are supported since PHP-5.6 - const ARR = array('this', 'is', array('an', 'array')); + const ARR = ['this', 'is', ['an', 'array']]; // Enumerators will be generated from public constants only public const PUBLIC_CONST = 'public constant'; // this will be an enumerator @@ -58,9 +56,9 @@ class UserStatus extends Enum private const PRIVATE_CONST = 'private constant'; // this will NOT be an enumerator // works since PHP-7.0 - see https://wiki.php.net/rfc/context_sensitive_lexer - const TRUE = true; - const FALSE = false; - const NULL = null; + const TRUE = 'true'; + const FALSE = 'false'; + const NULL = 'null'; const PUBLIC = 'public'; const PRIVATE = 'private'; const PROTECTED = 'protected'; @@ -85,11 +83,11 @@ $status->getName(); // returns the selected constant name $status->getOrdinal(); // returns the ordinal number of the selected constant // basic methods to list defined enumerators -UserStatus::getEnumerators() // returns a list of enumerator instances -UserStatus::getValues() // returns a list of enumerator values -UserStatus::getNames() // returns a list of enumerator names -UserStatus::getOrdinals() // returns a list of ordinal numbers -UserStatus::getConstants() // returns an associative array of enumerator names to enumerator values +UserStatus::getEnumerators(); // returns a list of enumerator instances +UserStatus::getValues(); // returns a list of enumerator values +UserStatus::getNames(); // returns a list of enumerator names +UserStatus::getOrdinals(); // returns a list of ordinal numbers +UserStatus::getConstants(); // returns an associative array of enumerator names to enumerator values // same enumerators (of the same enumeration class) holds the same instance UserStatus::get(UserStatus::ACTIVE) === UserStatus::ACTIVE() @@ -135,11 +133,12 @@ Because in normal OOP the above example allows `UserStatus` and types inherited Please think about the following example: ```php -class ExtendedUserStatus +class ExtendedUserStatus extends UserStatus { const EXTENDED = 'extended'; } +$user = new User(); $user->setStatus(ExtendedUserStatus::EXTENDED()); ``` @@ -209,10 +208,10 @@ $enumSet->isEqual($other); // Check if the EnumSet is the same as other $enumSet->isSubset($other); // Check if the EnumSet is a subset of other $enumSet->isSuperset($other); // Check if the EnumSet is a superset of other -$enumSet->union($other[, ...]); // Produce a new set with enumerators from both this and other (this | other) -$enumSet->intersect($other[, ...]); // Produce a new set with enumerators common to both this and other (this & other) -$enumSet->diff($other[, ...]); // Produce a new set with enumerators in this but not in other (this - other) -$enumSet->symDiff($other[, ...]); // Produce a new set with enumerators in either this and other but not in both (this ^ (other | other)) +$enumSet->union($other); // Produce a new set with enumerators from both this and other (this | other) +$enumSet->intersect($other); // Produce a new set with enumerators common to both this and other (this & other) +$enumSet->diff($other); // Produce a new set with enumerators in this but not in other (this - other) +$enumSet->symDiff($other); // Produce a new set with enumerators in either this and other but not in both (this ^ other) ``` ## EnumMap @@ -227,20 +226,48 @@ use MabeEnum\EnumMap; // create a new EnumMap $enumMap = new EnumMap('UserStatus'); -// attach entries (by value or by instance) -$enumMap->attach(UserStatus::INACTIVE, 'inaktiv'); -$enumMap->attach(UserStatus::ACTIVE(), 'aktiv'); -$enumMap->attach(UserStatus::DELETED(), 'gelöscht'); +// read and write key-value-pairs like an array +$enumMap[UserStatus::INACTIVE] = 'inaktiv'; +$enumMap[UserStatus::ACTIVE] = 'aktiv'; +$enumMap[UserStatus::DELETED] = 'gelöscht'; +$enumMap[UserStatus::INACTIVE]; // 'inaktiv'; +$enumMap[UserStatus::ACTIVE]; // 'aktiv'; +$enumMap[UserStatus::DELETED]; // 'gelöscht'; + +isset($enumMap[UserStatus::DELETED]); // true +unset($enumMap[UserStatus::DELETED]); +isset($enumMap[UserStatus::DELETED]); // false + +// ... no matter if you use enumerator values or enumerator objects +$enumMap[UserStatus::INACTIVE()] = 'inaktiv'; +$enumMap[UserStatus::ACTIVE()] = 'aktiv'; +$enumMap[UserStatus::DELETED()] = 'gelöscht'; +$enumMap[UserStatus::INACTIVE()]; // 'inaktiv'; +$enumMap[UserStatus::ACTIVE()]; // 'aktiv'; +$enumMap[UserStatus::DELETED()]; // 'gelöscht'; + +isset($enumMap[UserStatus::DELETED()]); // true +unset($enumMap[UserStatus::DELETED()]); +isset($enumMap[UserStatus::DELETED()]); // false -// detach entries (by value or by instance) -$enumMap->detach(UserStatus::INACTIVE); -$enumMap->detach(UserStatus::DELETED()); -// iterate +// support for null aware exists check +$enumMap[UserStatus::NULL] = null; +isset($enumMap[UserStatus::NULL]); // false +$enumMap->contains(UserStatus::NULL); // true + + +// iterating over the map foreach ($enumMap as $enum => $value) { - var_dump(get_class($enum)); // UserStatus - var_dump(gettype($value)) // string + get_class($enum); // UserStatus (enumerator object) + gettype($value); // string (the value the enumerators maps to) } + +// get a list of keys (= a list of enumerator objects) +$enumMap->getKeys(); + +// get a list of values (= a list of values the enumerator maps to) +$enumMap->getValues(); ``` ## Serializing diff --git a/bench/EnumMapBench.php b/bench/EnumMapBench.php index 6ca356c8..60ab9ca2 100644 --- a/bench/EnumMapBench.php +++ b/bench/EnumMapBench.php @@ -53,6 +53,36 @@ public function init() } } + public function benchGetKeysEmpty() + { + $this->emptyMap->getKeys(); + } + + public function benchGetKeysFull() + { + $this->fullMap->getKeys(); + } + + public function benchGetValuesEmpty() + { + $this->emptyMap->getValues(); + } + + public function benchGetValuesFull() + { + $this->fullMap->getValues(); + } + + public function benchSearchTypeJuggling() + { + $this->fullMap->search('31'); + } + + public function benchSearchStrict() + { + $this->fullMap->search(31, true); + } + public function benchOffsetSetEnumerator() { foreach ($this->enumerators as $enumerator) { @@ -81,31 +111,31 @@ public function benchOffsetUnsetValue() } } - public function benchOffsetExistsEnumeratorTrue() + public function benchOffsetExistsEnumerator() { foreach ($this->enumerators as $enumerator) { $this->fullMap->offsetExists($enumerator); } } - public function benchOffsetExistsEnumeratorFalse() + public function benchOffsetExistsValue() { - foreach ($this->enumerators as $enumerator) { - $this->fullMap->offsetExists($enumerator); + foreach ($this->values as $value) { + $this->fullMap->offsetExists($value); } } - public function benchOffsetExistsValueTrue() + public function benchContainsEnumerator() { - foreach ($this->values as $value) { - $this->fullMap->offsetExists($value); + foreach ($this->enumerators as $enumerator) { + $this->fullMap->contains($enumerator); } } - public function benchOffsetExistsValueFalse() + public function benchContainsValue() { foreach ($this->values as $value) { - $this->fullMap->offsetExists($value); + $this->fullMap->contains($value); } } diff --git a/composer.json b/composer.json index b12f5046..2ff4f6e8 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,6 @@ "php": ">=5.6", "ext-reflection": "*" }, - "suggest": { - "php": "PHP>=5.4 will be required for using trait EnumSerializableTrait" - }, "require-dev": { "phpunit/phpunit": "^5.7 || ^6.0", "phpbench/phpbench": "@dev", diff --git a/src/Enum.php b/src/Enum.php index e76a7f1a..85dc7cf6 100644 --- a/src/Enum.php +++ b/src/Enum.php @@ -247,7 +247,7 @@ final public static function byOrdinal($ordinal) } if (!isset(self::$names[$class][$ordinal])) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( 'Invalid ordinal number, must between 0 and %s', \count(self::$names[$class]) - 1 )); diff --git a/src/EnumMap.php b/src/EnumMap.php index e2824222..2cc37749 100644 --- a/src/EnumMap.php +++ b/src/EnumMap.php @@ -2,18 +2,21 @@ namespace MabeEnum; -use SplObjectStorage; +use ArrayAccess; +use Countable; use InvalidArgumentException; +use OutOfBoundsException; +use SeekableIterator; +use UnexpectedValueException; /** - * A map of enumerator keys of the given enumeration (EnumMap) - * based on SplObjectStorage + * A map of enumerators (EnumMap) and mixed values. * * @link http://github.com/marc-mabe/php-enum for the canonical source repository * @copyright Copyright (c) 2017 Marc Bennewitz * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License */ -class EnumMap extends SplObjectStorage +class EnumMap implements ArrayAccess, Countable, SeekableIterator { /** * The classname of the enumeration type @@ -21,6 +24,24 @@ class EnumMap extends SplObjectStorage */ private $enumeration; + /** + * Internal map of ordinal number and value + * @var array + */ + private $map = []; + + /** + * List of ordinal numbers + * @var int[] + */ + private $ordinals = []; + + /** + * Current iterator position + * @var int + */ + private $pos = 0; + /** * Constructor * @param string $enumeration The classname of the enumeration type @@ -29,7 +50,7 @@ class EnumMap extends SplObjectStorage public function __construct($enumeration) { if (!\is_subclass_of($enumeration, Enum::class)) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( "This EnumMap can handle subclasses of '%s' only", Enum::class )); @@ -47,55 +68,74 @@ public function getEnumeration() } /** - * Attach a new enumerator or overwrite an existing one - * @param Enum|null|boolean|int|float|string $enumerator - * @param mixed $data - * @return void - * @throws InvalidArgumentException On an invalid given enumerator + * Get a list of map keys + * @return Enum[] */ - public function attach($enumerator, $data = null) + public function getKeys() { - $enumeration = $this->enumeration; - parent::attach($enumeration::get($enumerator), $data); + return \array_map([$this->enumeration, 'byOrdinal'], $this->ordinals); + } + + /** + * Get a list of map values + * @return mixed[] + */ + public function getValues() + { + return \array_values($this->map); + } + + /** + * Search for the given value + * @param mixed $value + * @param bool $strict Use strict type comparison + * @return Enum|null The found key or NULL + */ + public function search($value, $strict = false) + { + $ord = \array_search($value, $this->map, $strict); + if ($ord !== false) { + $enumeration = $this->enumeration; + return $enumeration::byOrdinal($ord); + } + + return null; } /** * Test if the given enumerator exists * @param Enum|null|boolean|int|float|string $enumerator * @return boolean + * @see offsetExists */ public function contains($enumerator) { try { $enumeration = $this->enumeration; - return parent::contains($enumeration::get($enumerator)); + $ord = $enumeration::get($enumerator)->getOrdinal(); + return array_key_exists($ord, $this->map); } catch (InvalidArgumentException $e) { - // On an InvalidArgumentException the given argument can't be contained in this map + // An invalid enumerator can't be contained in this map return false; } } /** - * Detach an enumerator - * @param Enum|null|boolean|int|float|string $enumerator - * @return void - * @throws InvalidArgumentException On an invalid given enumerator - */ - public function detach($enumerator) - { - $enumeration = $this->enumeration; - parent::detach($enumeration::get($enumerator)); - } - - /** - * Test if the given enumerator exists + * Test if the given enumerator key exists and is not NULL * @param Enum|null|boolean|int|float|string $enumerator * @return boolean - * @see contains() + * @see contains */ public function offsetExists($enumerator) { - return $this->contains($enumerator); + try { + $enumeration = $this->enumeration; + $ord = $enumeration::get($enumerator)->getOrdinal(); + return isset($this->map[$ord]); + } catch (InvalidArgumentException $e) { + // An invalid enumerator can't be an offset of this map + return false; + } } /** @@ -107,21 +147,34 @@ public function offsetExists($enumerator) public function offsetGet($enumerator) { $enumeration = $this->enumeration; - return parent::offsetGet($enumeration::get($enumerator)); + $ord = $enumeration::get($enumerator)->getOrdinal(); + if (!isset($this->map[$ord]) && !array_key_exists($ord, $this->map)) { + throw new UnexpectedValueException(\sprintf( + "Enumerator '%s' could not be found", + \is_object($enumerator) ? $enumerator->getValue() : $enumerator + )); + } + + return $this->map[$ord]; } /** * Attach a new enumerator or overwrite an existing one * @param Enum|null|boolean|int|float|string $enumerator - * @param mixed $data + * @param mixed $value * @return void * @throws InvalidArgumentException On an invalid given enumerator * @see attach() */ - public function offsetSet($enumerator, $data = null) + public function offsetSet($enumerator, $value = null) { $enumeration = $this->enumeration; - parent::offsetSet($enumeration::get($enumerator), $data); + $ord = $enumeration::get($enumerator)->getOrdinal(); + + if (!array_key_exists($ord, $this->map)) { + $this->ordinals[] = $ord; + } + $this->map[$ord] = $value; } /** @@ -134,7 +187,26 @@ public function offsetSet($enumerator, $data = null) public function offsetUnset($enumerator) { $enumeration = $this->enumeration; - parent::offsetUnset($enumeration::get($enumerator)); + $ord = $enumeration::get($enumerator)->getOrdinal(); + + if (($idx = \array_search($ord, $this->ordinals, true)) !== false) { + unset($this->map[$ord], $this->ordinals[$idx]); + $this->ordinals = \array_values($this->ordinals); + } + } + + /** + * Seeks to the given iterator position. + * @param int $pos + */ + public function seek($pos) + { + $pos = (int)$pos; + if (!isset($this->ordinals[$pos])) { + throw new OutOfBoundsException("Position {$pos} not found"); + } + + $this->pos = $pos; } /** @@ -143,7 +215,11 @@ public function offsetUnset($enumerator) */ public function current() { - return parent::getInfo(); + if (!isset($this->ordinals[$this->pos])) { + return null; + } + + return $this->map[$this->ordinals[$this->pos]]; } /** @@ -152,6 +228,48 @@ public function current() */ public function key() { - return parent::current(); + if (!isset($this->ordinals[$this->pos])) { + return null; + } + + $enumeration = $this->enumeration; + return $enumeration::byOrdinal($this->ordinals[$this->pos]); + } + + /** + * Reset the iterator position to zero. + * @return void + */ + public function rewind() + { + $this->pos = 0; + } + + /** + * Increment the iterator position by one. + * @return void + */ + public function next() + { + ++$this->pos; + } + + /** + * Test if the iterator is in a valid state + * @return boolean + */ + public function valid() + { + return isset($this->ordinals[$this->pos]); + } + + /** + * Count the number of elements + * + * @return int + */ + public function count() + { + return \count($this->ordinals); } } diff --git a/src/EnumSet.php b/src/EnumSet.php index 6fe1935d..93e55598 100644 --- a/src/EnumSet.php +++ b/src/EnumSet.php @@ -232,7 +232,7 @@ private function doRewindInt() } /** - * Test if the iterator in a valid state + * Test if the iterator is in a valid state * @return boolean */ public function valid() @@ -377,7 +377,7 @@ public function isSuperset(EnumSet $other) public function union(EnumSet $other) { if ($this->enumeration !== $other->enumeration) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); @@ -397,7 +397,7 @@ public function union(EnumSet $other) public function intersect(EnumSet $other) { if ($this->enumeration !== $other->enumeration) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); @@ -417,7 +417,7 @@ public function intersect(EnumSet $other) public function diff(EnumSet $other) { if ($this->enumeration !== $other->enumeration) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); @@ -437,7 +437,7 @@ public function diff(EnumSet $other) public function symDiff(EnumSet $other) { if ($this->enumeration !== $other->enumeration) { - throw new InvalidArgumentException(sprintf( + throw new InvalidArgumentException(\sprintf( 'Other should be of the same enumeration as this %s', $this->enumeration )); diff --git a/tests/MabeEnumTest/EnumMapTest.php b/tests/MabeEnumTest/EnumMapTest.php index 29b90f07..490e76d6 100644 --- a/tests/MabeEnumTest/EnumMapTest.php +++ b/tests/MabeEnumTest/EnumMapTest.php @@ -6,9 +6,9 @@ use MabeEnum\EnumMap; use MabeEnumTest\TestAsset\EnumBasic; use MabeEnumTest\TestAsset\EnumInheritance; +use OutOfBoundsException; use PHPUnit\Framework\TestCase; -use ReflectionClass; -use Serializable; +use UnexpectedValueException; /** * Unit tests for the class MabeEnum\EnumMap @@ -19,37 +19,48 @@ */ class EnumMapTest extends TestCase { - public function testBasic() + public function testBasicWithEnumeratorInstances() { $enumMap = new EnumMap(EnumBasic::class); $this->assertSame(EnumBasic::class, $enumMap->getEnumeration()); - $enum1 = EnumBasic::ONE(); + $enum1 = EnumBasic::TWO(); $value1 = 'value1'; - $enum2 = EnumBasic::TWO(); + $enum2 = EnumBasic::ONE(); $value2 = 'value2'; $this->assertFalse($enumMap->contains($enum1)); - $this->assertNull($enumMap->attach($enum1, $value1)); + $this->assertFalse($enumMap->contains($enum2)); + $this->assertSame([], $enumMap->getKeys()); + $this->assertSame([], $enumMap->getValues()); + + $this->assertNull($enumMap->offsetSet($enum1, $value1)); $this->assertTrue($enumMap->contains($enum1)); $this->assertSame($value1, $enumMap[$enum1]); - $this->assertSame(spl_object_hash($enum1), $enumMap->getHash($enum1)); - $this->assertFalse($enumMap->contains($enum2)); - $this->assertNull($enumMap->attach($enum2, $value2)); + $this->assertSame([$enum1], $enumMap->getKeys()); + $this->assertSame([$value1], $enumMap->getValues()); + + + $this->assertNull($enumMap->offsetSet($enum2, $value2)); $this->assertTrue($enumMap->contains($enum2)); $this->assertSame($value2, $enumMap[$enum2]); - $this->assertSame(spl_object_hash($enum2), $enumMap->getHash($enum2)); + $this->assertSame([$enum1, $enum2], $enumMap->getKeys()); + $this->assertSame([$value1, $value2], $enumMap->getValues()); - $this->assertNull($enumMap->detach($enum1)); + $this->assertNull($enumMap->offsetUnset($enum1)); $this->assertFalse($enumMap->contains($enum1)); + $this->assertSame([$enum2], $enumMap->getKeys()); + $this->assertSame([$value2], $enumMap->getValues()); - $this->assertNull($enumMap->detach($enum2)); + $this->assertNull($enumMap->offsetUnset($enum2)); $this->assertFalse($enumMap->contains($enum2)); + $this->assertSame([], $enumMap->getKeys()); + $this->assertSame([], $enumMap->getValues()); } - public function testBasicWithConstantValuesAsEnums() + public function testBasicWithEnumeratorValues() { $enumMap = new EnumMap(EnumBasic::class); @@ -60,20 +71,40 @@ public function testBasicWithConstantValuesAsEnums() $value2 = 'value2'; $this->assertFalse($enumMap->contains($enum1)); - $this->assertNull($enumMap->attach($enum1, $value1)); + $this->assertFalse($enumMap->contains($enum2)); + $this->assertSame([], $enumMap->getKeys()); + $this->assertSame([], $enumMap->getValues()); + + $this->assertNull($enumMap->offsetSet($enum1, $value1)); $this->assertTrue($enumMap->contains($enum1)); $this->assertSame($value1, $enumMap[$enum1]); - $this->assertFalse($enumMap->contains($enum2)); - $this->assertNull($enumMap->attach($enum2, $value2)); + $this->assertSame([EnumBasic::byValue($enum1)], $enumMap->getKeys()); + $this->assertSame([$value1], $enumMap->getValues()); + + $this->assertNull($enumMap->offsetSet($enum2, $value2)); $this->assertTrue($enumMap->contains($enum2)); $this->assertSame($value2, $enumMap[$enum2]); + $this->assertSame([EnumBasic::byValue($enum1), EnumBasic::byValue($enum2)], $enumMap->getKeys()); + $this->assertSame([$value1, $value2], $enumMap->getValues()); - $this->assertNull($enumMap->detach($enum1)); + $this->assertNull($enumMap->offsetUnset($enum1)); $this->assertFalse($enumMap->contains($enum1)); + $this->assertSame([EnumBasic::byValue($enum2)], $enumMap->getKeys()); + $this->assertSame([$value2], $enumMap->getValues()); - $this->assertNull($enumMap->detach($enum2)); + $this->assertNull($enumMap->offsetUnset($enum2)); $this->assertFalse($enumMap->contains($enum2)); + $this->assertSame([], $enumMap->getKeys()); + $this->assertSame([], $enumMap->getValues()); + } + + public function testOffsetGetMissingKey() + { + $enumMap = new EnumMap(EnumBasic::class); + + $this->expectException(UnexpectedValueException::class); + $enumMap->offsetGet(EnumBasic::ONE); } public function testIterate() @@ -92,8 +123,8 @@ public function testIterate() $this->assertFalse($enumMap->valid()); // attach - $enumMap->attach($enum1, $value1); - $enumMap->attach($enum2, $value2); + $enumMap->offsetSet($enum1, $value1); + $enumMap->offsetSet($enum2, $value2); // a not empty enum map should be valid, starting by 0 (if not iterated) $enumMap->rewind(); @@ -110,6 +141,7 @@ public function testIterate() // go to the next element (out of range) $this->assertNull($enumMap->next()); + $this->assertNull($enumMap->current()); $this->assertFalse($enumMap->valid()); $this->assertSame(null, $enumMap->key()); @@ -183,12 +215,112 @@ public function testContainsAndOffsetExistsReturnsFalseOnInvalidEnum() $this->assertFalse(isset($enumMap[EnumInheritance::INHERITANCE])); } + public function testSearch() + { + $enumMap = new EnumMap(EnumBasic::class); + $enumMap[EnumBasic::TWO()] = '2'; + $enumMap[EnumBasic::THREE()] = '3'; + + $this->assertSame(EnumBasic::TWO(), $enumMap->search('2')); + $this->assertSame(EnumBasic::TWO(), $enumMap->search(2)); + $this->assertSame(EnumBasic::THREE(), $enumMap->search('3')); + $this->assertSame(EnumBasic::THREE(), $enumMap->search(3)); + + $this->assertNull($enumMap->search('4')); + $this->assertNull($enumMap->search(4)); + $this->assertNull($enumMap->search('unknown')); + } + + public function testSearchStrict() + { + $enumMap = new EnumMap(EnumBasic::class); + $enumMap[EnumBasic::TWO()] = '2'; + $enumMap[EnumBasic::THREE()] = '3'; + + $this->assertSame(EnumBasic::TWO(), $enumMap->search('2', true)); + $this->assertNull($enumMap->search(2, true)); + $this->assertSame(EnumBasic::THREE(), $enumMap->search('3', true)); + $this->assertNull($enumMap->search(3, true)); + + $this->assertNull($enumMap->search('4', true)); + $this->assertNull($enumMap->search(4, true)); + $this->assertNull($enumMap->search('unknown', true)); + } + + public function testNullValue() + { + $enumMap = new EnumMap(EnumBasic::class); + $enumMap[EnumBasic::ONE()] = null; + + $this->assertSame(1, $enumMap->count()); + $this->assertNull($enumMap[EnumBasic::ONE]); + $this->assertNull($enumMap->offsetGet(EnumBasic::ONE)); + $this->assertSame([EnumBasic::ONE()], $enumMap->getKeys()); + + $enumMap->rewind(); + $this->assertSame(1, $enumMap->count()); + $this->assertTrue($enumMap->valid()); + $this->assertSame(EnumBasic::ONE(), $enumMap->key()); + $this->assertNull($enumMap->current()); + + $this->assertFalse(isset($enumMap[EnumBasic::ONE])); + $this->assertFalse(isset($enumMap[EnumBasic::ONE()])); + $this->assertFalse($enumMap->offsetExists(EnumBasic::ONE)); + $this->assertFalse($enumMap->offsetExists(EnumBasic::ONE())); + $this->assertTrue($enumMap->contains(EnumBasic::ONE)); + $this->assertTrue($enumMap->contains(EnumBasic::ONE())); + + // add the same enumeration a second time should do nothing + $enumMap->offsetSet(EnumBasic::ONE(), null); + $this->assertSame(1, $enumMap->count()); + $this->assertSame([EnumBasic::ONE()], $enumMap->getKeys()); + + // overwrite by non null value + $enumMap->offsetSet(EnumBasic::ONE(), false); + $this->assertSame(1, $enumMap->count()); + $this->assertSame([EnumBasic::ONE()], $enumMap->getKeys()); + + $this->assertSame(1, $enumMap->count()); + $this->assertTrue($enumMap->valid()); + $this->assertSame(EnumBasic::ONE(), $enumMap->key()); + $this->assertFalse($enumMap->current()); + + $this->assertTrue(isset($enumMap[EnumBasic::ONE])); + $this->assertTrue(isset($enumMap[EnumBasic::ONE()])); + $this->assertTrue($enumMap->offsetExists(EnumBasic::ONE)); + $this->assertTrue($enumMap->offsetExists(EnumBasic::ONE())); + $this->assertTrue($enumMap->contains(EnumBasic::ONE)); + $this->assertTrue($enumMap->contains(EnumBasic::ONE())); + } + + public function testSeek() + { + $enumMap = new EnumMap(EnumBasic::class); + $enumMap[EnumBasic::ONE()] = 'one'; + $enumMap[EnumBasic::TWO()] = 'two'; + + $this->assertSame(EnumBasic::ONE(), $enumMap->key()); + + $enumMap->seek(1); + $this->assertSame(EnumBasic::TWO(), $enumMap->key()); + + $enumMap->seek(0); + $this->assertSame(EnumBasic::ONE(), $enumMap->key()); + + $this->expectException(OutOfBoundsException::class); + $enumMap->seek(2); + } + public function testSerializable() { $enumMap = new EnumMap(EnumBasic::class); - if ($enumMap instanceof Serializable) { - $enumMap->offsetSet(EnumBasic::ONE, 'one'); - serialize($enumMap); - } + $enumMap[EnumBasic::ONE()] = 'one'; + + $enumMapCopy = unserialize(serialize($enumMap)); + $this->assertTrue($enumMapCopy->offsetExists(EnumBasic::ONE)); + $this->assertFalse($enumMapCopy->offsetExists(EnumBasic::TWO)); + + // unserialized instance should be the same + $this->assertSame(EnumBasic::ONE(), $enumMapCopy->key()); } }