Skip to content

Commit

Permalink
Refactor EnumMap - fixes #91
Browse files Browse the repository at this point in the history
  • Loading branch information
marc-mabe committed Oct 3, 2017
1 parent fc93d58 commit 8b1e418
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 40 deletions.
4 changes: 2 additions & 2 deletions bench/EnumMapBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public function init()

$this->emptyMap = new EnumMap(Enum66::class);
$this->fullMap = new EnumMap(Enum66::class);
foreach ($this->enumerators as $i => $enumerator) {
$this->fullMap->offsetSet($enumerator, $i);
foreach ($this->enumerators as $enumerator) {
$this->fullMap->offsetSet($enumerator);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
));
Expand Down
126 changes: 104 additions & 22 deletions src/EnumMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,45 @@

namespace MabeEnum;

use SplObjectStorage;
use ArrayAccess;
use Countable;
use InvalidArgumentException;
use Iterator;
use UnexpectedValueException;

/**
* A map of enumerator keys of the given enumeration (EnumMap<T>)
* based on SplObjectStorage
* A map of enumerators (EnumMap<T>) 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, Iterator
{
/**
* The classname of the enumeration type
* @var string
*/
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
Expand All @@ -29,7 +49,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
));
Expand All @@ -55,8 +75,7 @@ public function getEnumeration()
*/
public function attach($enumerator, $data = null)
{
$enumeration = $this->enumeration;
parent::attach($enumeration::get($enumerator), $data);
return $this->offsetSet($enumerator, $data);
}

/**
Expand All @@ -66,13 +85,7 @@ public function attach($enumerator, $data = null)
*/
public function contains($enumerator)
{
try {
$enumeration = $this->enumeration;
return parent::contains($enumeration::get($enumerator));
} catch (InvalidArgumentException $e) {
// On an InvalidArgumentException the given argument can't be contained in this map
return false;
}
return $this->offsetExists($enumerator);
}

/**
Expand All @@ -83,8 +96,7 @@ public function contains($enumerator)
*/
public function detach($enumerator)
{
$enumeration = $this->enumeration;
parent::detach($enumeration::get($enumerator));
$this->offsetUnset($enumerator);
}

/**
Expand All @@ -95,7 +107,14 @@ public function detach($enumerator)
*/
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 contained in this map
return false;
}
}

/**
Expand All @@ -107,7 +126,15 @@ 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])) {
throw new UnexpectedValueException(\sprintf(
"Enumerator '%s' could not be found",
\is_object($enumerator) ? $enumerator->getValue() : $enumerator
));
}

return $this->map[$ord];
}

/**
Expand All @@ -121,7 +148,12 @@ public function offsetGet($enumerator)
public function offsetSet($enumerator, $data = null)
{
$enumeration = $this->enumeration;
parent::offsetSet($enumeration::get($enumerator), $data);
$ord = $enumeration::get($enumerator)->getOrdinal();

if (!isset($this->map[$ord])) {
$this->ordinals[] = $ord;
}
$this->map[$ord] = $data;
}

/**
Expand All @@ -134,7 +166,11 @@ 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]);
}
}

/**
Expand All @@ -143,7 +179,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]];
}

/**
Expand All @@ -152,6 +192,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);
}
}
10 changes: 5 additions & 5 deletions src/EnumSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
));
Expand All @@ -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
));
Expand All @@ -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
));
Expand All @@ -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
));
Expand Down
22 changes: 12 additions & 10 deletions tests/MabeEnumTest/EnumMapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,26 @@
*/
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->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->assertTrue($enumMap->contains($enum2));
$this->assertSame($value2, $enumMap[$enum2]);
$this->assertSame(spl_object_hash($enum2), $enumMap->getHash($enum2));

$this->assertNull($enumMap->detach($enum1));
$this->assertFalse($enumMap->contains($enum1));
Expand All @@ -49,7 +47,7 @@ public function testBasic()
$this->assertFalse($enumMap->contains($enum2));
}

public function testBasicWithConstantValuesAsEnums()
public function testBasicWithEnumeratorValues()
{
$enumMap = new EnumMap(EnumBasic::class);

Expand Down Expand Up @@ -186,9 +184,13 @@ public function testContainsAndOffsetExistsReturnsFalseOnInvalidEnum()
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());
}
}

0 comments on commit 8b1e418

Please sign in to comment.