├── .gitignore ├── tests ├── MabeEnumTest │ ├── TestAsset │ │ ├── EmptyEnum.php │ │ ├── EnumInheritance.php │ │ ├── EnumAmbiguous.php │ │ └── EnumBasic.php │ ├── EnumTest.php │ ├── EnumSetTest.php │ └── EnumMapTest.php └── bootstrap.php ├── .travis.yml ├── phpunit.xml.dist ├── composer.json ├── LICENSE.txt ├── src └── MabeEnum │ ├── EnumSet.php │ ├── Enum.php │ └── EnumMap.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | #eclipse 2 | .project 3 | 4 | #phpstorm 5 | .idea 6 | 7 | #composer 8 | composer.lock 9 | composer.phar 10 | vendor 11 | -------------------------------------------------------------------------------- /tests/MabeEnumTest/TestAsset/EmptyEnum.php: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | ./src 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =5.3", 25 | "ext-reflection": "*" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": ">=3.6" 29 | }, 30 | "autoload": { 31 | "psr-0": { 32 | "MabeEnum\\": "src/" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Marc Bennewitz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the organisation nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/MabeEnum/EnumSet.php: -------------------------------------------------------------------------------- 1 | enumClass = $enumClass; 64 | 65 | if ($flags !== null) { 66 | $this->flags = (int) $flags; 67 | } 68 | } 69 | 70 | /** 71 | * Get the classname of enumeration this map is for 72 | * @return string 73 | */ 74 | public function getEnumClass() 75 | { 76 | return $this->enumClass; 77 | } 78 | 79 | /** 80 | * Get flags of defined behaviours 81 | * @return int 82 | */ 83 | public function getFlags() 84 | { 85 | return $this->flags; 86 | } 87 | 88 | /** 89 | * Attach a new enumeration or overwrite an existing one 90 | * @param Enum|scalar $enum 91 | * @return void 92 | * @throws InvalidArgumentException On an invalid given enum 93 | */ 94 | public function attach($enum) 95 | { 96 | $enum = $this->initEnum($enum); 97 | $ordinal = $enum->getOrdinal(); 98 | 99 | if (!($this->flags & self::UNIQUE) || !in_array($ordinal, $this->list, true)) { 100 | $this->list[] = $ordinal; 101 | 102 | if ($this->flags & self::ORDERED) { 103 | sort($this->list); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Test if the given enumeration exists 110 | * @param Enum|scalar $enum 111 | * @return boolean 112 | */ 113 | public function contains($enum) 114 | { 115 | $enum = $this->initEnum($enum); 116 | return in_array($enum->getOrdinal(), $this->list, true); 117 | } 118 | 119 | /** 120 | * Detach all enumerations same as the given enum 121 | * @param Enum|scalar $enum 122 | * @return void 123 | * @throws InvalidArgumentException On an invalid given enum 124 | */ 125 | public function detach($enum) 126 | { 127 | $enum = $this->initEnum($enum); 128 | 129 | while (($index = array_search($enum->getOrdinal(), $this->list, true)) !== false) { 130 | unset($this->list[$index]); 131 | } 132 | 133 | // reset index positions to have a real list 134 | $this->list = array_values($this->list); 135 | } 136 | 137 | /* Iterator */ 138 | 139 | /** 140 | * Get the current Enum 141 | * @return Enum|null Returns the current Enum or NULL on an invalid iterator position 142 | */ 143 | public function current() 144 | { 145 | if (!isset($this->list[$this->index])) { 146 | return null; 147 | } 148 | 149 | $enumClass = $this->enumClass; 150 | return $enumClass::getByOrdinal($this->list[$this->index]); 151 | } 152 | 153 | /** 154 | * Get the current iterator position 155 | * @return int 156 | */ 157 | public function key() 158 | { 159 | return $this->index; 160 | } 161 | 162 | public function next() 163 | { 164 | ++$this->index; 165 | } 166 | 167 | public function rewind() 168 | { 169 | $this->index = 0; 170 | } 171 | 172 | public function valid() 173 | { 174 | return isset($this->list[$this->index]); 175 | } 176 | 177 | /* Countable */ 178 | 179 | public function count() 180 | { 181 | return count($this->list); 182 | } 183 | 184 | /** 185 | * Initialize an enumeration 186 | * @param Enum|scalar $enum 187 | * @return Enum 188 | * @throws InvalidArgumentException On an invalid given enum 189 | */ 190 | private function initEnum($enum) 191 | { 192 | // auto instantiate 193 | if (is_scalar($enum)) { 194 | $enumClass = $this->enumClass; 195 | return $enumClass::get($enum); 196 | } 197 | 198 | // allow only enums of the same type 199 | // (don't allow instance of) 200 | $enumClass = get_class($enum); 201 | if ($enumClass && strcasecmp($enumClass, $this->enumClass) === 0) { 202 | return $enum; 203 | } 204 | 205 | throw new InvalidArgumentException(sprintf( 206 | "The given enum of type '%s' isn't same as the required type '%s'", 207 | get_class($enum) ?: gettype($enum), 208 | $this->enumClass 209 | )); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /tests/MabeEnumTest/EnumTest.php: -------------------------------------------------------------------------------- 1 | assertSame('ONE', $enum->getName()); 30 | } 31 | 32 | public function testToStringMagicMethodReturnsName() 33 | { 34 | $enum = EnumBasic::get(EnumBasic::ONE); 35 | $this->assertSame('ONE', $enum->__toString()); 36 | } 37 | 38 | public function testEnumInheritance() 39 | { 40 | $enum = EnumInheritance::get(EnumInheritance::ONE); 41 | $this->assertSame(array( 42 | 'ONE' => 1, 43 | 'TWO' => 2, 44 | 'THREE' => 3, 45 | 'FOUR' => 4, 46 | 'FIVE' => 5, 47 | 'SIX' => 6, 48 | 'SEVEN' => 7, 49 | 'EIGHT' => 8, 50 | 'NINE' => 9, 51 | 'ZERO' => 0, 52 | 'FLOAT' => 0.123, 53 | 'STR' => 'str', 54 | 'STR_EMPTY' => '', 55 | 'NIL' => null, 56 | 'BOOLEAN_TRUE' => true, 57 | 'BOOLEAN_FALSE' => false, 58 | 'INHERITANCE' => 'Inheritance', 59 | ), $enum::getConstants()); 60 | $this->assertSame(EnumInheritance::ONE, $enum->getValue()); 61 | $this->assertSame(0, $enum->getOrdinal()); 62 | 63 | $enum = EnumInheritance::get(EnumInheritance::INHERITANCE); 64 | $this->assertSame(EnumInheritance::INHERITANCE, $enum->getValue()); 65 | $this->assertSame(16, $enum->getOrdinal()); 66 | } 67 | 68 | public function testGetWithStrictValue() 69 | { 70 | $enum = EnumBasic::get(EnumBasic::ONE); 71 | $this->assertSame(1, $enum->getValue()); 72 | $this->assertSame(0, $enum->getOrdinal()); 73 | } 74 | 75 | public function testGetWithNonStrictValueThrowsInvalidArgumentException() 76 | { 77 | $this->setExpectedException('InvalidArgumentException'); 78 | EnumBasic::get((string)EnumBasic::TWO); 79 | } 80 | 81 | public function testGetWithInvalidValueThrowsInvalidArgumentException() 82 | { 83 | $this->setExpectedException('InvalidArgumentException'); 84 | EnumBasic::get('unknown'); 85 | } 86 | 87 | public function testGetWithInvalidTypeOfValueThrowsInvalidArgumentException() 88 | { 89 | $this->setExpectedException('InvalidArgumentException'); 90 | EnumBasic::get(array()); 91 | } 92 | 93 | public function testGetAllValues() 94 | { 95 | $constants = EnumBasic::getConstants(); 96 | foreach ($constants as $name => $value) { 97 | $enum = EnumBasic::get($value); 98 | $this->assertSame($value, $enum->getValue()); 99 | $this->assertSame($name, $enum->getName()); 100 | } 101 | } 102 | 103 | public function testCallingGetOrdinalTwoTimesWillResultTheSameValue() 104 | { 105 | $enum = EnumBasic::get(EnumBasic::TWO); 106 | $this->assertSame(1, $enum->getOrdinal()); 107 | $this->assertSame(1, $enum->getOrdinal()); 108 | } 109 | 110 | public function testInstantiateUsingOrdinalNumber() 111 | { 112 | $enum = EnumInheritance::getByOrdinal(16); 113 | $this->assertSame(16, $enum->getOrdinal()); 114 | $this->assertSame('INHERITANCE', $enum->getName()); 115 | } 116 | 117 | public function testInstantiateUsingInvalidOrdinalNumberThrowsInvalidArgumentException() 118 | { 119 | $this->setExpectedException('InvalidArgumentException'); 120 | EnumInheritance::getByOrdinal(17); 121 | } 122 | 123 | public function testInstantiateByName() 124 | { 125 | $enum = EnumInheritance::getByName('ONE'); 126 | $this->assertInstanceOf('MabeEnumTest\TestAsset\EnumInheritance', $enum); 127 | $this->assertSame(EnumInheritance::ONE, $enum->getValue()); 128 | } 129 | 130 | public function testInstantiateByUnknownNameThrowsInvalidArgumentException() 131 | { 132 | $this->setExpectedException('InvalidArgumentException'); 133 | EnumInheritance::getByName('UNKNOWN'); 134 | } 135 | 136 | public function testInstantiateUsingMagicMethod() 137 | { 138 | $enum = EnumInheritance::ONE(); 139 | $this->assertInstanceOf('MabeEnumTest\TestAsset\EnumInheritance', $enum); 140 | $this->assertSame(EnumInheritance::ONE, $enum->getValue()); 141 | } 142 | 143 | public function testAmbuguousConstantsThrowsLogicException() 144 | { 145 | $this->setExpectedException('LogicException'); 146 | EnumAmbiguous::get('unknown'); 147 | } 148 | 149 | public function testSingleton() 150 | { 151 | $enum1 = EnumBasic::get(EnumBasic::ONE); 152 | $enum2 = EnumBasic::ONE(); 153 | $this->assertSame($enum1, $enum2); 154 | } 155 | 156 | public function testClear() 157 | { 158 | $enum1 = EnumBasic::ONE(); 159 | EnumBasic::clear(); 160 | $enum2 = EnumBasic::ONE(); 161 | $enum3 = EnumBasic::ONE(); 162 | 163 | $this->assertNotSame($enum1, $enum2); 164 | $this->assertSame($enum2, $enum3); 165 | } 166 | 167 | public function testCloneNotCallableAndThrowsLogicException() 168 | { 169 | $enum = EnumBasic::ONE(); 170 | 171 | $reflectionClass = new ReflectionClass($enum); 172 | $reflectionMethod = $reflectionClass->getMethod('__clone'); 173 | $this->assertTrue($reflectionMethod->isPrivate(), 'The method __clone must be private'); 174 | $this->assertTrue($reflectionMethod->isFinal(), 'The method __clone must be final'); 175 | 176 | $reflectionMethod->setAccessible(true); 177 | $this->setExpectedException('LogicException'); 178 | $reflectionMethod->invoke($enum); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-enum 2 | [![Build Status](https://secure.travis-ci.org/marc-mabe/php-enum.png?branch=master)](http://travis-ci.org/marc-mabe/php-enum) 3 | [![Quality Score](https://scrutinizer-ci.com/g/marc-mabe/php-enum/badges/quality-score.png?s=7dfddb19a12314ecc5f05eeb2b297bdde3ad2623)](https://scrutinizer-ci.com/g/marc-mabe/php-enum/) 4 | [![Coverage Status](https://coveralls.io/repos/marc-mabe/php-enum/badge.png?branch=master)](https://coveralls.io/r/marc-mabe/php-enum?branch=master) 5 | [![Total Downloads](https://poser.pugx.org/marc-mabe/php-enum/downloads.png)](https://packagist.org/packages/marc-mabe/php-enum) 6 | [![Latest Stable](https://poser.pugx.org/marc-mabe/php-enum/v/stable.png)](https://packagist.org/packages/marc-mabe/php-enum) 7 | [![Dependency Status](https://www.versioneye.com/php/marc-mabe:php-enum/dev-master/badge.png)](https://www.versioneye.com/php/marc-mabe:php-enum/dev-master) 8 | 9 | This is a native PHP implementation to add enumeration support to PHP >= 5.3. 10 | It's an abstract class that needs to be extended to use it. 11 | 12 | 13 | # What is an Enumeration? 14 | 15 | [Wikipedia](http://wikipedia.org/wiki/Enumerated_type) 16 | > In computer programming, an enumerated type (also called enumeration or enum) 17 | > is a data type consisting of a set of named values called elements, members 18 | > or enumerators of the type. The enumerator names are usually identifiers that 19 | > behave as constants in the language. A variable that has been declared as 20 | > having an enumerated type can be assigned any of the enumerators as a value. 21 | > In other words, an enumerated type has values that are different from each 22 | > other, and that can be compared and assigned, but which do not have any 23 | > particular concrete representation in the computer's memory; compilers and 24 | > interpreters can represent them arbitrarily. 25 | 26 | 27 | # Usage 28 | 29 | ## Basics 30 | 31 | use MabeEnum\Enum; 32 | 33 | // define an own enumeration class 34 | class UserStatus extends Enum 35 | { 36 | const INACTIVE = 0; 37 | const ACTIVE = 1; 38 | const DELETED = 2; 39 | 40 | // all scalar datatypes are supported 41 | const NIL = null; 42 | const BOOLEAN = true; 43 | const INT = 1234; 44 | const STR = 'string'; 45 | const FLOAT = 0.123; 46 | } 47 | 48 | // different ways to instantiate an enumeration 49 | $status = UserStatus::get(UserStatus::ACTIVE); 50 | $status = UserStatus::ACTIVE(); 51 | $status = UserStatus::getByName('ACTIVE'); 52 | $status = UserStatus::getByOrdinal(1); 53 | 54 | // available methods to get the selected entry 55 | $status->getValue(); // returns the selected constant value 56 | $status->getName(); // returns the selected constant name 57 | $status->getOrdinal(); // returns the ordinal number of the selected constant 58 | (string) $status; // returns the selected constant name 59 | 60 | // same enumerations of the same class holds the same instance 61 | UserStatus::get(UserStatus::ACTIVE) === UserStatus::ACTIVE() 62 | UserStatus::get(UserStatus::DELETED) != UserStatus::INACTIVE() 63 | 64 | 65 | ## Type-Hint 66 | 67 | use MabeEnum\Enum; 68 | use UserStatus; 69 | 70 | class User 71 | { 72 | protected $status; 73 | 74 | public function setStatus(UserStatus $status) 75 | { 76 | $this->status = $status; 77 | } 78 | 79 | public function getStatus() 80 | { 81 | if (!$this->status) { 82 | // initialize a default value 83 | $this->status = UserStatus::get(UserStatus::INACTIVE); 84 | } 85 | return $this->status; 86 | } 87 | } 88 | 89 | ## EnumMap 90 | 91 | An ```EnumMap``` maps enumeration instances of exactly one type to data assigned to. 92 | Internally the ```EnumMap``` is based of ```SplObjectStorage```. 93 | 94 | use MabeEnum\EnumMap; 95 | use UserStatus; 96 | 97 | // create a new EnumMap 98 | $enumMap = new EnumMap('UserStatus'); 99 | 100 | // attach entries (by value or by instance) 101 | $enumMap->attach(UserStatus::INACTIVE, 'inaktiv'); 102 | $enumMap->attach(UserStatus::ACTIVE(), 'aktiv'); 103 | $enumMap->attach(UserStatus::DELETED(), 'gelöscht'); 104 | 105 | // detach entries (by value or by instance) 106 | $enumMap->detach(UserStatus::INACTIVE); 107 | $enumMap->detach(UserStatus::DELETED()); 108 | 109 | // iterate 110 | var_dump(iterator_to_array($enumSet)); // array(0 => UserStatus{$value=1}); 111 | 112 | // define key and value used for iteration 113 | $enumSet->setFlags(EnumSet::KEY_AS_NAME | EnumSet::CURRENT_AS_DATA); 114 | var_dump(iterator_to_array($enumSet)); // array('ACTIVE' => 'aktiv'); 115 | 116 | 117 | ## EnumSet 118 | 119 | An ```EnumSet``` groups enumeration instances of exactly one type together. 120 | Internally it's based of a list (array) of ordinal values. 121 | 122 | use MabeEnum\EnumSet; 123 | use UserStatus; 124 | 125 | // create a new EnumSet 126 | $enumSet = new EnumSet('UserStatus'); 127 | 128 | // attach entries (by value or by instance) 129 | $enumSet->attach(UserStatus::INACTIVE); 130 | $enumSet->attach(UserStatus::ACTIVE()); 131 | $enumSet->attach(UserStatus::DELETED()); 132 | 133 | // detach entries (by value or by instance) 134 | $enumSet->detach(UserStatus::INACTIVE); 135 | $enumSet->detach(UserStatus::DELETED()); 136 | 137 | // iterate 138 | var_dump(iterator_to_array($enumSet)); // array(0 => UserStatus{$value=1}); 139 | 140 | 141 | # Why not ```SplEnum``` 142 | 143 | * ```SplEnum``` is not build-in into PHP and requires pecl extension installed. 144 | * Instances of the same value of an ```SplEnum``` are not the same instance. 145 | * ```SplEnum``` doesn't have implemented ```EnumMap``` or ```EnumSet```. 146 | 147 | 148 | # Install 149 | 150 | ## Composer 151 | 152 | Add ```marc-mabe/php-enum``` to the project's composer.json dependencies and run 153 | ```php composer.phar install``` 154 | 155 | ## GIT 156 | 157 | ```git clone git://github.com/marc-mabe/php-enum.git``` 158 | 159 | ## ZIP / TAR 160 | 161 | Download the last version from [Github](https://github.com/marc-mabe/php-enum/tags) 162 | and extract it. 163 | 164 | 165 | # New BSD License 166 | 167 | The files in this archive are released under the New BSD License. 168 | You can find a copy of this license in LICENSE.txt file. 169 | -------------------------------------------------------------------------------- /src/MabeEnum/Enum.php: -------------------------------------------------------------------------------- 1 | ["$const" => $value, ...], ...] 36 | */ 37 | private static $constants = array(); 38 | 39 | /** 40 | * Already instantiated enums 41 | * 42 | * @var array ["$class" => ["$const" => $instance, ...], ...] 43 | */ 44 | private static $instances = array(); 45 | 46 | /** 47 | * Constructor 48 | * 49 | * @param scalar $value The value to select 50 | * @param int|null $ordinal 51 | */ 52 | final private function __construct($value, $ordinal = null) 53 | { 54 | $this->value = $value; 55 | $this->ordinal = $ordinal; 56 | } 57 | 58 | /** 59 | * Get the current selected constant name 60 | * 61 | * @return string 62 | * @see getName() 63 | */ 64 | public function __toString() 65 | { 66 | return $this->getName(); 67 | } 68 | 69 | /** 70 | * @throws LogicException Enums are not cloneable 71 | * because instances are implemented as singletons 72 | */ 73 | final private function __clone() 74 | { 75 | throw new LogicException('Enums are not cloneable'); 76 | } 77 | 78 | /** 79 | * Get the current selected value 80 | * 81 | * @return mixed 82 | */ 83 | final public function getValue() 84 | { 85 | return $this->value; 86 | } 87 | 88 | /** 89 | * Get the current selected constant name 90 | * 91 | * @return string 92 | */ 93 | final public function getName() 94 | { 95 | return array_search($this->value, self::detectConstants(get_called_class()), true); 96 | } 97 | 98 | /** 99 | * Get the ordinal number of the selected value 100 | * 101 | * @return int 102 | */ 103 | final public function getOrdinal() 104 | { 105 | if ($this->ordinal !== null) { 106 | return $this->ordinal; 107 | } 108 | 109 | // detect ordinal 110 | $ordinal = 0; 111 | $value = $this->value; 112 | foreach (self::detectConstants(get_called_class()) as $constValue) { 113 | if ($value === $constValue) { 114 | break; 115 | } 116 | ++$ordinal; 117 | } 118 | 119 | $this->ordinal = $ordinal; 120 | return $ordinal; 121 | } 122 | 123 | /** 124 | * Get an enum of the given value 125 | * 126 | * @param scalar $value 127 | * @return Enum 128 | * @throws InvalidArgumentException On an unknwon or invalid value 129 | * @throws LogicException On ambiguous constant values 130 | */ 131 | final static public function get($value) 132 | { 133 | $class = get_called_class(); 134 | $constants = self::detectConstants($class); 135 | $name = array_search($value, $constants, true); 136 | if ($name === false) { 137 | if (is_scalar($value)) { 138 | throw new InvalidArgumentException('Unknown value ' . var_export($value, true)); 139 | } else { 140 | throw new InvalidArgumentException('Invalid value of type ' . gettype($value)); 141 | } 142 | } 143 | 144 | if (isset(self::$instances[$class][$name])) { 145 | return self::$instances[$class][$name]; 146 | } 147 | 148 | return self::$instances[$class][$name] = new $class($constants[$name]); 149 | } 150 | 151 | /** 152 | * Get an enum by the given name 153 | * 154 | * @param string $name The name to instantiate the enum by 155 | * @return Enum 156 | * @throws InvalidArgumentException On an invalid or unknown name 157 | * @throws LogicException On ambiguous constant values 158 | */ 159 | final public static function getByName($name) 160 | { 161 | $name = (string) $name; 162 | $class = get_called_class(); 163 | if (isset(self::$instances[$class][$name])) { 164 | return self::$instances[$class][$name]; 165 | } 166 | 167 | $const = $class . '::' . $name; 168 | if (!defined($const)) { 169 | throw new InvalidArgumentException($const . ' not defined'); 170 | } 171 | 172 | return self::$instances[$class][$name] = new $class(constant($const)); 173 | } 174 | 175 | /** 176 | * Get an enum by the given ordinal number 177 | * 178 | * @param int $ordinal The ordinal number to instantiate the enum by 179 | * @return Enum 180 | * @throws InvalidArgumentException On an invalid ordinal number 181 | * @throws LogicException On ambiguous constant values 182 | */ 183 | final public static function getByOrdinal($ordinal) 184 | { 185 | $ordinal = (int) $ordinal; 186 | $class = get_called_class(); 187 | $constants = self::detectConstants($class); 188 | $item = array_slice($constants, $ordinal, 1, false); 189 | if (!$item) { 190 | throw new InvalidArgumentException(sprintf( 191 | 'Invalid ordinal number, must between 0 and %s', 192 | count($constants) - 1 193 | )); 194 | } 195 | 196 | $name = key($item); 197 | if (isset(self::$instances[$class][$name])) { 198 | return self::$instances[$class][$name]; 199 | } 200 | 201 | return self::$instances[$class][$name] = new $class(current($item), $ordinal); 202 | } 203 | 204 | /** 205 | * Clears all instantiated enums 206 | * 207 | * NOTE: This can break singleton behavior ... use it with caution! 208 | * 209 | * @return void 210 | */ 211 | final static public function clear() 212 | { 213 | $class = get_called_class(); 214 | unset(self::$instances[$class], self::$constants[$class]); 215 | } 216 | 217 | /** 218 | * Get all available constants of the called class 219 | * 220 | * @return array 221 | * @throws LogicException On ambiguous constant values 222 | */ 223 | final static public function getConstants() 224 | { 225 | return self::detectConstants(get_called_class()); 226 | } 227 | 228 | /** 229 | * Detect constants available by given class 230 | * 231 | * @param string $class 232 | * @return array 233 | * @throws LogicException On ambiguous constant values 234 | */ 235 | static private function detectConstants($class) 236 | { 237 | if (!isset(self::$constants[$class])) { 238 | $reflection = new ReflectionClass($class); 239 | $constants = $reflection->getConstants(); 240 | 241 | // values needs to be unique 242 | $ambiguous = array(); 243 | foreach ($constants as $value) { 244 | $names = array_keys($constants, $value, true); 245 | if (count($names) > 1) { 246 | $ambiguous[var_export($value, true)] = $names; 247 | } 248 | } 249 | if ($ambiguous) { 250 | throw new LogicException( 251 | 'All possible values needs to be unique. The following are ambiguous: ' 252 | . implode(', ', array_map(function ($names) use ($constants) { 253 | return implode('/', $names) . '=' . var_export($constants[$names[0]], true); 254 | }, $ambiguous)) 255 | ); 256 | } 257 | 258 | // This is required to make sure that constants of base classes will be the first 259 | while (($reflection = $reflection->getParentClass()) && $reflection->name != __CLASS__) { 260 | $constants = $reflection->getConstants() + $constants; 261 | } 262 | 263 | self::$constants[$class] = $constants; 264 | } 265 | 266 | return self::$constants[$class]; 267 | } 268 | 269 | /** 270 | * Instantiate an enum by a name of a constant. 271 | * 272 | * This will be called automatically on calling a method 273 | * with the same name of a defined constant. 274 | * 275 | * @param string $method The name to instantiate the enum by (called as method) 276 | * @param array $args There should be no arguments 277 | * @return Enum 278 | * @throws InvalidArgumentException On an invalid or unknown name 279 | * @throws LogicException On ambiguous constant values 280 | */ 281 | final public static function __callStatic($method, array $args) 282 | { 283 | return self::getByName($method); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/MabeEnum/EnumMap.php: -------------------------------------------------------------------------------- 1 | enumClass = $enumClass; 92 | 93 | if ($flags !== null) { 94 | $this->setFlags($flags); 95 | } 96 | } 97 | 98 | /** 99 | * Get the classname of enumeration this map is for 100 | * @return string 101 | */ 102 | public function getEnumClass() 103 | { 104 | return $this->enumClass; 105 | } 106 | 107 | /** 108 | * Set behaviour flags 109 | * see KEY_AS_* and CURRENT_AS_* constants 110 | * @param int $flags 111 | * @return void 112 | * @throws InvalidArgumentException On invalid or unsupported flags 113 | */ 114 | public function setFlags($flags) 115 | { 116 | $flags = (int)$flags; 117 | 118 | $keyFlag = $flags & 7; 119 | if ($keyFlag > 4) { 120 | throw new InvalidArgumentException( 121 | "Unsupported flag given for key() behavior" 122 | ); 123 | } elseif (!$keyFlag) { 124 | $keyFlag = $this->flags & 7; 125 | } 126 | 127 | 128 | $currentFlag = $flags & 56; 129 | if ($currentFlag > 40) { 130 | throw new InvalidArgumentException( 131 | "Unsupported flag given for current() behavior" 132 | ); 133 | } elseif (!$currentFlag) { 134 | $currentFlag = $this->flags & 56; 135 | } 136 | 137 | $this->flags = $keyFlag | $currentFlag; 138 | } 139 | 140 | /** 141 | * Get the behaviour flags 142 | * @return int 143 | */ 144 | public function getFlags() 145 | { 146 | return $this->flags; 147 | } 148 | 149 | /** 150 | * Attach a new enumeration or overwrite an existing one 151 | * @param Enum|scalar $enum 152 | * @param mixed $data 153 | * @return void 154 | * @throws InvalidArgumentException On an invalid given enum 155 | */ 156 | public function attach($enum, $data = null) 157 | { 158 | $this->initEnum($enum); 159 | parent::attach($enum, $data); 160 | } 161 | 162 | /** 163 | * Test if the given enumeration exists 164 | * @param Enum|scalar $enum 165 | * @return boolean 166 | */ 167 | public function contains($enum) 168 | { 169 | try { 170 | $this->initEnum($enum); 171 | return parent::contains($enum); 172 | } catch (InvalidArgumentException $e) { 173 | // On an InvalidArgumentException the given argument can't be contained in this map 174 | return false; 175 | } 176 | } 177 | 178 | /** 179 | * Detach an enumeration 180 | * @param Enum|scalar $enum 181 | * @return void 182 | * @throws InvalidArgumentException On an invalid given enum 183 | */ 184 | public function detach($enum) 185 | { 186 | $this->initEnum($enum); 187 | parent::detach($enum); 188 | } 189 | 190 | /** 191 | * Get a unique identifier for the given enumeration 192 | * @param Enum|scalar $enum 193 | * @return string 194 | * @throws InvalidArgumentException On an invalid given enum 195 | */ 196 | public function getHash($enum) 197 | { 198 | $this->initEnum($enum); 199 | 200 | // getHash is available since PHP 5.4 201 | return spl_object_hash($enum); 202 | } 203 | 204 | /** 205 | * Test if the given enumeration exists 206 | * @param Enum|scalar $enum 207 | * @return boolean 208 | * @see contains() 209 | */ 210 | public function offsetExists($enum) 211 | { 212 | return $this->contains($enum); 213 | } 214 | 215 | /** 216 | * Get mapped data for this given enum 217 | * @param Enum|scalar $enum 218 | * @return mixed 219 | * @throws InvalidArgumentException On an invalid given enum 220 | */ 221 | public function offsetGet($enum) 222 | { 223 | $this->initEnum($enum); 224 | return parent::offsetGet($enum); 225 | } 226 | 227 | /** 228 | * Attach a new enumeration or overwrite an existing one 229 | * @param Enum|scalar $enum 230 | * @param mixed $data 231 | * @return void 232 | * @throws InvalidArgumentException On an invalid given enum 233 | * @see attach() 234 | */ 235 | public function offsetSet($enum, $data = null) 236 | { 237 | $this->initEnum($enum); 238 | parent::offsetSet($enum, $data); 239 | } 240 | 241 | /** 242 | * Detach an existing enumeration 243 | * @param Enum|scalar $enum 244 | * @return void 245 | * @throws InvalidArgumentException On an invalid given enum 246 | * @see detach() 247 | */ 248 | public function offsetUnset($enum) 249 | { 250 | $this->initEnum($enum); 251 | parent::offsetUnset($enum); 252 | } 253 | 254 | /** 255 | * Get the current item 256 | * The return value varied by the behaviour of the current flag 257 | * @return mixed 258 | */ 259 | public function current() 260 | { 261 | switch ($this->flags & 120) { 262 | case self::CURRENT_AS_ENUM: 263 | return parent::current(); 264 | case self::CURRENT_AS_DATA: 265 | return parent::getInfo(); 266 | case self::CURRENT_AS_VALUE: 267 | return parent::current()->getValue(); 268 | case self::CURRENT_AS_NAME: 269 | return parent::current()->getName(); 270 | case self::CURRENT_AS_ORDINAL: 271 | return parent::current()->getOrdinal(); 272 | default: 273 | throw new RuntimeException('Invalid current flag'); 274 | } 275 | } 276 | 277 | /** 278 | * Get the current item-key 279 | * The return value varied by the behaviour of the key flag 280 | * @return scalar 281 | */ 282 | public function key() 283 | { 284 | switch ($this->flags & 7) { 285 | case self::KEY_AS_INDEX: 286 | return parent::key(); 287 | case self::KEY_AS_NAME: 288 | return parent::current()->getName(); 289 | case self::KEY_AS_ORDINAL: 290 | return parent::current()->getOrdinal(); 291 | case self::KEY_AS_VALUE: 292 | return parent::current()->getValue(); 293 | default: 294 | throw new RuntimeException('Invalid key flag'); 295 | } 296 | } 297 | 298 | /** 299 | * Initialize an enumeration 300 | * @param Enum|scalar $enum 301 | * @return Enum 302 | * @throws InvalidArgumentException On an invalid given enum 303 | */ 304 | private function initEnum(&$enum) 305 | { 306 | // auto instantiate 307 | if (is_scalar($enum)) { 308 | $enumClass = $this->enumClass; 309 | $enum = $enumClass::get($enum); 310 | return; 311 | } 312 | 313 | // allow only enums of the same type 314 | // (don't allow instance of) 315 | $enumClass = get_class($enum); 316 | if ($enumClass && strcasecmp($enumClass, $this->enumClass) === 0) { 317 | return; 318 | } 319 | 320 | throw new InvalidArgumentException(sprintf( 321 | "The given enum of type '%s' isn't same as the required type '%s'", 322 | get_class($enum) ?: gettype($enum), 323 | $this->enumClass 324 | )); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /tests/MabeEnumTest/EnumSetTest.php: -------------------------------------------------------------------------------- 1 | assertSame('MabeEnumTest\TestAsset\EnumBasic', $enumSet->getEnumClass()); 24 | $this->assertSame(EnumSet::UNIQUE, $enumSet->getFlags()); 25 | 26 | $enum1 = EnumBasic::ONE(); 27 | $enum2 = EnumBasic::TWO(); 28 | 29 | $this->assertFalse($enumSet->contains($enum1)); 30 | $this->assertNull($enumSet->attach($enum1)); 31 | $this->assertTrue($enumSet->contains($enum1)); 32 | 33 | $this->assertFalse($enumSet->contains($enum2)); 34 | $this->assertNull($enumSet->attach($enum2)); 35 | $this->assertTrue($enumSet->contains($enum2)); 36 | 37 | $this->assertNull($enumSet->detach($enum1)); 38 | $this->assertFalse($enumSet->contains($enum1)); 39 | 40 | $this->assertNull($enumSet->detach($enum2)); 41 | $this->assertFalse($enumSet->contains($enum2)); 42 | } 43 | 44 | public function testBasicWithConstantValuesAsEnums() 45 | { 46 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic'); 47 | 48 | $enum1 = EnumBasic::ONE; 49 | $enum2 = EnumBasic::TWO; 50 | 51 | $this->assertFalse($enumSet->contains($enum1)); 52 | $this->assertNull($enumSet->attach($enum1)); 53 | $this->assertTrue($enumSet->contains($enum1)); 54 | 55 | $this->assertFalse($enumSet->contains($enum2)); 56 | $this->assertNull($enumSet->attach($enum2)); 57 | $this->assertTrue($enumSet->contains($enum2)); 58 | 59 | $this->assertNull($enumSet->detach($enum1)); 60 | $this->assertFalse($enumSet->contains($enum1)); 61 | 62 | $this->assertNull($enumSet->detach($enum2)); 63 | $this->assertFalse($enumSet->contains($enum2)); 64 | } 65 | 66 | public function testUnique() 67 | { 68 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic', EnumSet::UNIQUE); 69 | $this->assertSame(EnumSet::UNIQUE, $enumSet->getFlags()); 70 | 71 | $enumSet->attach(EnumBasic::ONE()); 72 | $enumSet->attach(EnumBasic::ONE); 73 | 74 | $enumSet->attach(EnumBasic::TWO()); 75 | $enumSet->attach(EnumBasic::TWO); 76 | 77 | $this->assertSame(2, $enumSet->count()); 78 | } 79 | 80 | public function testNotUnique() 81 | { 82 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic', 0); 83 | $this->assertSame(0, $enumSet->getFlags()); 84 | 85 | $enumSet->attach(EnumBasic::ONE()); 86 | $enumSet->attach(EnumBasic::ONE); 87 | 88 | $enumSet->attach(EnumBasic::TWO()); 89 | $enumSet->attach(EnumBasic::TWO); 90 | 91 | $this->assertSame(4, $enumSet->count()); 92 | 93 | // detch remove all 94 | $enumSet->detach(EnumBasic::ONE); 95 | $this->assertSame(2, $enumSet->count()); 96 | } 97 | 98 | public function testIterateUnordered() 99 | { 100 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic', EnumSet::UNIQUE); 101 | 102 | $enum1 = EnumBasic::ONE(); 103 | $enum2 = EnumBasic::TWO(); 104 | 105 | // an empty enum set needs to be invalid, starting by 0 106 | $this->assertSame(0, $enumSet->count()); 107 | $this->assertFalse($enumSet->valid()); 108 | $this->assertNull($enumSet->current()); 109 | 110 | // attach 111 | $enumSet->attach($enum1); 112 | $enumSet->attach($enum2); 113 | 114 | // a not empty enum map should be valid, starting by 0 (if not iterated) 115 | $this->assertSame(2, $enumSet->count()); 116 | $this->assertTrue($enumSet->valid()); 117 | $this->assertSame(0, $enumSet->key()); 118 | $this->assertSame($enum1, $enumSet->current()); 119 | 120 | // go to the next element (last) 121 | $this->assertNull($enumSet->next()); 122 | $this->assertTrue($enumSet->valid()); 123 | $this->assertSame(1, $enumSet->key()); 124 | $this->assertSame($enum2, $enumSet->current()); 125 | 126 | // go to the next element (out of range) 127 | $this->assertNull($enumSet->next()); 128 | $this->assertFalse($enumSet->valid()); 129 | $this->assertSame(2, $enumSet->key()); 130 | $this->assertNull($enumSet->current()); 131 | 132 | // rewind will set the iterator position back to 0 133 | $enumSet->rewind(); 134 | $this->assertTrue($enumSet->valid()); 135 | $this->assertSame(0, $enumSet->key()); 136 | $this->assertSame($enum1, $enumSet->current()); 137 | } 138 | 139 | public function testIterateOrdered() 140 | { 141 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic', EnumSet::UNIQUE | EnumSet::ORDERED); 142 | 143 | $enum1 = EnumBasic::ONE(); 144 | $enum2 = EnumBasic::TWO(); 145 | 146 | // an empty enum set needs to be invalid, starting by 0 147 | $this->assertSame(0, $enumSet->count()); 148 | $this->assertFalse($enumSet->valid()); 149 | $this->assertNull($enumSet->current()); 150 | 151 | // attach 152 | $enumSet->attach($enum2); 153 | $enumSet->attach($enum1); 154 | 155 | // a not empty enum map should be valid, starting by 0 (if not iterated) 156 | $this->assertSame(2, $enumSet->count()); 157 | $this->assertTrue($enumSet->valid()); 158 | $this->assertSame(0, $enumSet->key()); 159 | $this->assertSame($enum1, $enumSet->current()); 160 | 161 | // go to the next element (last) 162 | $this->assertNull($enumSet->next()); 163 | $this->assertTrue($enumSet->valid()); 164 | $this->assertSame(1, $enumSet->key()); 165 | $this->assertSame($enum2, $enumSet->current()); 166 | 167 | // go to the next element (out of range) 168 | $this->assertNull($enumSet->next()); 169 | $this->assertFalse($enumSet->valid()); 170 | $this->assertSame(2, $enumSet->key()); 171 | $this->assertNull($enumSet->current()); 172 | 173 | // rewind will set the iterator position back to 0 174 | $enumSet->rewind(); 175 | $this->assertTrue($enumSet->valid()); 176 | $this->assertSame(0, $enumSet->key()); 177 | $this->assertSame($enum1, $enumSet->current()); 178 | } 179 | 180 | public function testIterateOrderedNotUnique() 181 | { 182 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic', EnumSet::ORDERED); 183 | 184 | $enum1 = EnumBasic::ONE(); 185 | $enum2 = EnumBasic::TWO(); 186 | 187 | // an empty enum set needs to be invalid, starting by 0 188 | $this->assertSame(0, $enumSet->count()); 189 | $this->assertFalse($enumSet->valid()); 190 | $this->assertNull($enumSet->current()); 191 | 192 | // attach 193 | $enumSet->attach($enum2); 194 | $enumSet->attach($enum1); 195 | $enumSet->attach($enum2); 196 | $enumSet->attach($enum1); 197 | 198 | // index 0 199 | $this->assertSame(4, $enumSet->count()); 200 | $this->assertTrue($enumSet->valid()); 201 | $this->assertSame(0, $enumSet->key()); 202 | $this->assertSame($enum1, $enumSet->current()); 203 | 204 | // index 1 205 | $this->assertNull($enumSet->next()); 206 | $this->assertTrue($enumSet->valid()); 207 | $this->assertSame(1, $enumSet->key()); 208 | $this->assertSame($enum1, $enumSet->current()); 209 | 210 | // index 2 211 | $this->assertNull($enumSet->next()); 212 | $this->assertTrue($enumSet->valid()); 213 | $this->assertSame(2, $enumSet->key()); 214 | $this->assertSame($enum2, $enumSet->current()); 215 | 216 | // index 3 (last) 217 | $this->assertNull($enumSet->next()); 218 | $this->assertTrue($enumSet->valid()); 219 | $this->assertSame(3, $enumSet->key()); 220 | $this->assertSame($enum2, $enumSet->current()); 221 | 222 | // go to the next element (out of range) 223 | $this->assertNull($enumSet->next()); 224 | $this->assertFalse($enumSet->valid()); 225 | $this->assertSame(4, $enumSet->key()); 226 | $this->assertNull($enumSet->current()); 227 | 228 | // rewind will set the iterator position back to 0 229 | $enumSet->rewind(); 230 | $this->assertTrue($enumSet->valid()); 231 | $this->assertSame(0, $enumSet->key()); 232 | $this->assertSame($enum1, $enumSet->current()); 233 | } 234 | 235 | public function testIterateAndDetach() 236 | { 237 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumInheritance'); 238 | 239 | $enum1 = EnumInheritance::ONE(); 240 | $enum2 = EnumInheritance::TWO(); 241 | $enum3 = EnumInheritance::INHERITANCE(); 242 | 243 | // attach 244 | $enumSet->attach($enum1); 245 | $enumSet->attach($enum2); 246 | $enumSet->attach($enum3); 247 | 248 | // index 1 249 | $enumSet->next(); 250 | $this->assertSame($enum2, $enumSet->current()); 251 | 252 | // detach enum of current index 253 | $enumSet->detach($enumSet->current()); 254 | $this->assertSame($enum3, $enumSet->current()); 255 | 256 | // detach enum of current index if the last index 257 | $enumSet->detach($enumSet->current()); 258 | $this->assertFalse($enumSet->valid()); 259 | $this->assertNull($enumSet->current()); 260 | } 261 | 262 | public function testConstructThrowsInvalidArgumentExceptionIfEnumClassDoesNotExtendBaseEnum() 263 | { 264 | $this->setExpectedException('InvalidArgumentException'); 265 | new EnumSet('stdClass'); 266 | } 267 | 268 | public function testInitEnumThrowsInvalidArgumentExceptionOnInvalidEnum() 269 | { 270 | $enumSet = new EnumSet('MabeEnumTest\TestAsset\EnumBasic'); 271 | $this->setExpectedException('InvalidArgumentException'); 272 | $this->assertFalse($enumSet->contains(EnumInheritance::INHERITANCE())); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /tests/MabeEnumTest/EnumMapTest.php: -------------------------------------------------------------------------------- 1 | assertSame('MabeEnumTest\TestAsset\EnumBasic', $enumMap->getEnumClass()); 25 | 26 | $enum1 = EnumBasic::ONE(); 27 | $value1 = 'value1'; 28 | 29 | $enum2 = EnumBasic::TWO(); 30 | $value2 = 'value2'; 31 | 32 | $this->assertFalse($enumMap->contains($enum1)); 33 | $this->assertNull($enumMap->attach($enum1, $value1)); 34 | $this->assertTrue($enumMap->contains($enum1)); 35 | $this->assertSame($value1, $enumMap[$enum1]); 36 | $this->assertSame(spl_object_hash($enum1), $enumMap->getHash($enum1)); 37 | 38 | $this->assertFalse($enumMap->contains($enum2)); 39 | $this->assertNull($enumMap->attach($enum2, $value2)); 40 | $this->assertTrue($enumMap->contains($enum2)); 41 | $this->assertSame($value2, $enumMap[$enum2]); 42 | $this->assertSame(spl_object_hash($enum2), $enumMap->getHash($enum2)); 43 | 44 | $this->assertNull($enumMap->detach($enum1)); 45 | $this->assertFalse($enumMap->contains($enum1)); 46 | 47 | $this->assertNull($enumMap->detach($enum2)); 48 | $this->assertFalse($enumMap->contains($enum2)); 49 | } 50 | 51 | public function testBasicWithConstantValuesAsEnums() 52 | { 53 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 54 | 55 | $enum1 = EnumBasic::ONE; 56 | $value1 = 'value1'; 57 | 58 | $enum2 = EnumBasic::TWO; 59 | $value2 = 'value2'; 60 | 61 | $this->assertFalse($enumMap->contains($enum1)); 62 | $this->assertNull($enumMap->attach($enum1, $value1)); 63 | $this->assertTrue($enumMap->contains($enum1)); 64 | $this->assertSame($value1, $enumMap[$enum1]); 65 | $this->assertSame(spl_object_hash(EnumBasic::ONE()), $enumMap->getHash($enum1)); 66 | 67 | $this->assertFalse($enumMap->contains($enum2)); 68 | $this->assertNull($enumMap->attach($enum2, $value2)); 69 | $this->assertTrue($enumMap->contains($enum2)); 70 | $this->assertSame($value2, $enumMap[$enum2]); 71 | $this->assertSame(spl_object_hash(EnumBasic::TWO()), $enumMap->getHash($enum2)); 72 | 73 | $this->assertNull($enumMap->detach($enum1)); 74 | $this->assertFalse($enumMap->contains($enum1)); 75 | 76 | $this->assertNull($enumMap->detach($enum2)); 77 | $this->assertFalse($enumMap->contains($enum2)); 78 | } 79 | 80 | public function testIterate() 81 | { 82 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 83 | 84 | $enum1 = EnumBasic::ONE(); 85 | $value1 = 'value1'; 86 | 87 | $enum2 = EnumBasic::TWO(); 88 | $value2 = 'value2'; 89 | 90 | // an empty enum map needs to be invalid, starting by 0 91 | $enumMap->rewind(); 92 | $this->assertSame(0, $enumMap->count()); 93 | $this->assertFalse($enumMap->valid()); 94 | 95 | // attach 96 | $enumMap->attach($enum1, $value2); 97 | $enumMap->attach($enum2, $value1); 98 | 99 | // a not empty enum map should be valid, starting by 0 (if not iterated) 100 | $enumMap->rewind(); 101 | $this->assertSame(2, $enumMap->count()); 102 | $this->assertTrue($enumMap->valid()); 103 | $this->assertSame(0, $enumMap->key()); 104 | $this->assertSame($enum1, $enumMap->current()); 105 | 106 | // go to the next element (last) 107 | $this->assertNull($enumMap->next()); 108 | $this->assertTrue($enumMap->valid()); 109 | $this->assertSame(1, $enumMap->key()); 110 | $this->assertSame($enum2, $enumMap->current()); 111 | 112 | // go to the next element (out of range) 113 | $this->assertNull($enumMap->next()); 114 | $this->assertFalse($enumMap->valid()); 115 | $this->assertSame(2, $enumMap->key()); 116 | 117 | // rewind will set the iterator position back to 0 118 | $enumMap->rewind(); 119 | $this->assertTrue($enumMap->valid()); 120 | $this->assertSame(0, $enumMap->key()); 121 | $this->assertSame($enum1, $enumMap->current()); 122 | } 123 | 124 | public function testIterateWithFlags() 125 | { 126 | $enumMap = new EnumMap( 127 | 'MabeEnumTest\TestAsset\EnumBasic', 128 | EnumMap::KEY_AS_INDEX | EnumMap::CURRENT_AS_ENUM 129 | ); 130 | 131 | $enumMap->attach(EnumBasic::TWO(), 'first'); 132 | $enumMap->attach(EnumBasic::ONE(), 'second'); 133 | 134 | // EnumMap::KEY_AS_INDEX | EnumMap::CURRENT_AS_ENUM (first) 135 | $this->assertSame(EnumMap::KEY_AS_INDEX | EnumMap::CURRENT_AS_ENUM, $enumMap->getFlags()); 136 | 137 | $enumMap->rewind(); 138 | $this->assertSame(0, $enumMap->key()); 139 | $this->assertSame(EnumBasic::TWO(), $enumMap->current()); 140 | 141 | $enumMap->next(); 142 | $this->assertSame(1, $enumMap->key()); 143 | $this->assertSame(EnumBasic::ONE(), $enumMap->current()); 144 | 145 | // EnumMap::KEY_AS_NAME | EnumMap::CURRENT_AS_DATA 146 | $enumMap->setFlags(EnumMap::KEY_AS_NAME | EnumMap::CURRENT_AS_DATA); 147 | $this->assertSame(EnumMap::KEY_AS_NAME | EnumMap::CURRENT_AS_DATA, $enumMap->getFlags()); 148 | 149 | $enumMap->rewind(); 150 | $this->assertSame('TWO', $enumMap->key()); 151 | $this->assertSame('first', $enumMap->current()); 152 | 153 | $enumMap->next(); 154 | $this->assertSame('ONE', $enumMap->key()); 155 | $this->assertSame('second', $enumMap->current()); 156 | 157 | // EnumMap::KEY_AS_VALUE | EnumMap::CURRENT_AS_ORDINAL 158 | $enumMap->setFlags(EnumMap::KEY_AS_VALUE | EnumMap::CURRENT_AS_ORDINAL); 159 | $this->assertSame(EnumMap::KEY_AS_VALUE | EnumMap::CURRENT_AS_ORDINAL, $enumMap->getFlags()); 160 | 161 | $enumMap->rewind(); 162 | $this->assertSame(2, $enumMap->key()); 163 | $this->assertSame(1, $enumMap->current()); 164 | 165 | $enumMap->next(); 166 | $this->assertSame(1, $enumMap->key()); 167 | $this->assertSame(0, $enumMap->current()); 168 | 169 | // EnumMap::KEY_AS_ORDINAL | EnumMap::CURRENT_AS_VALUE 170 | $enumMap->setFlags(EnumMap::KEY_AS_ORDINAL | EnumMap::CURRENT_AS_VALUE); 171 | $this->assertSame(EnumMap::KEY_AS_ORDINAL | EnumMap::CURRENT_AS_VALUE, $enumMap->getFlags()); 172 | 173 | $enumMap->rewind(); 174 | $this->assertSame(1, $enumMap->key()); 175 | $this->assertSame(2, $enumMap->current()); 176 | 177 | $enumMap->next(); 178 | $this->assertSame(0, $enumMap->key()); 179 | $this->assertSame(1, $enumMap->current()); 180 | 181 | // only change current flag to EnumMap::CURRENT_AS_NAME 182 | $enumMap->setFlags(EnumMap::CURRENT_AS_NAME); 183 | $this->assertSame(EnumMap::KEY_AS_ORDINAL | EnumMap::CURRENT_AS_NAME, $enumMap->getFlags()); 184 | 185 | $enumMap->rewind(); 186 | $this->assertSame(1, $enumMap->key()); 187 | $this->assertSame('TWO', $enumMap->current()); 188 | 189 | $enumMap->next(); 190 | $this->assertSame(0, $enumMap->key()); 191 | $this->assertSame('ONE', $enumMap->current()); 192 | 193 | // only change key flag to EnumMap::NAME_AS_NAME 194 | $enumMap->setFlags(EnumMap::KEY_AS_NAME); 195 | $this->assertSame(EnumMap::KEY_AS_NAME | EnumMap::CURRENT_AS_NAME, $enumMap->getFlags()); 196 | 197 | $enumMap->rewind(); 198 | $this->assertSame('TWO', $enumMap->key()); 199 | $this->assertSame('TWO', $enumMap->current()); 200 | 201 | $enumMap->next(); 202 | $this->assertSame('ONE', $enumMap->key()); 203 | $this->assertSame('ONE', $enumMap->current()); 204 | } 205 | 206 | public function testArrayAccessWithObjects() 207 | { 208 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 209 | 210 | $enumMap[EnumBasic::ONE()] = 'first'; 211 | $enumMap[EnumBasic::TWO()] = 'second'; 212 | 213 | $this->assertTrue(isset($enumMap[EnumBasic::ONE()])); 214 | $this->assertTrue(isset($enumMap[EnumBasic::TWO()])); 215 | 216 | $this->assertSame('first', $enumMap[EnumBasic::ONE()]); 217 | $this->assertSame('second', $enumMap[EnumBasic::TWO()]); 218 | 219 | unset($enumMap[EnumBasic::ONE()], $enumMap[EnumBasic::TWO()]); 220 | 221 | $this->assertFalse(isset($enumMap[EnumBasic::ONE()])); 222 | $this->assertFalse(isset($enumMap[EnumBasic::TWO()])); 223 | } 224 | 225 | public function testArrayAccessWithValues() 226 | { 227 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 228 | 229 | $enumMap[EnumBasic::ONE] = 'first'; 230 | $enumMap[EnumBasic::TWO] = 'second'; 231 | 232 | $this->assertTrue(isset($enumMap[EnumBasic::ONE])); 233 | $this->assertTrue(isset($enumMap[EnumBasic::TWO])); 234 | 235 | $this->assertSame('first', $enumMap[EnumBasic::ONE]); 236 | $this->assertSame('second', $enumMap[EnumBasic::TWO]); 237 | 238 | unset($enumMap[EnumBasic::ONE], $enumMap[EnumBasic::TWO]); 239 | 240 | $this->assertFalse(isset($enumMap[EnumBasic::ONE])); 241 | $this->assertFalse(isset($enumMap[EnumBasic::TWO])); 242 | } 243 | 244 | public function testConstructThrowsInvalidArgumentExceptionIfEnumClassDoesNotExtendBaseEnum() 245 | { 246 | $this->setExpectedException('InvalidArgumentException'); 247 | new EnumMap('stdClass'); 248 | } 249 | 250 | public function testSetFlagsThrowsInvalidArgumentExceptionOnUnsupportedKeyFlag() 251 | { 252 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 253 | 254 | $this->setExpectedException('InvalidArgumentException'); 255 | $enumMap->setFlags(5); 256 | } 257 | 258 | public function testCurrentThrowsRuntimeExceptionOnInvalidFlag() 259 | { 260 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 261 | $enumMap->attach(EnumBasic::ONE()); 262 | $enumMap->rewind(); 263 | 264 | // change internal flags to an invalid current flag 265 | $reflectionClass = new ReflectionClass($enumMap); 266 | $reflectionProp = $reflectionClass->getProperty('flags'); 267 | $reflectionProp->setAccessible(true); 268 | $reflectionProp->setValue($enumMap, 0); 269 | 270 | $this->setExpectedException('RuntimeException'); 271 | $enumMap->current(); 272 | } 273 | 274 | public function testKeyThrowsRuntimeExceptionOnInvalidFlag() 275 | { 276 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 277 | $enumMap->attach(EnumBasic::ONE()); 278 | $enumMap->rewind(); 279 | 280 | // change internal flags to an invalid current flag 281 | $reflectionClass = new ReflectionClass($enumMap); 282 | $reflectionProp = $reflectionClass->getProperty('flags'); 283 | $reflectionProp->setAccessible(true); 284 | $reflectionProp->setValue($enumMap, 0); 285 | 286 | $this->setExpectedException('RuntimeException'); 287 | $enumMap->key(); 288 | } 289 | 290 | public function testSetFlagsThrowsInvalidArgumentExceptionOnUnsupportedCurrentFlag() 291 | { 292 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 293 | 294 | $this->setExpectedException('InvalidArgumentException'); 295 | $enumMap->setFlags(48); 296 | } 297 | 298 | public function testInitEnumThrowsInvalidArgumentExceptionOnInvalidEnumGiven() 299 | { 300 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 301 | 302 | $this->setExpectedException('InvalidArgumentException'); 303 | $enumMap->offsetSet(EnumInheritance::INHERITANCE(), 'test'); 304 | } 305 | 306 | public function testContainsAndOffsetExistsReturnsFalseOnInvalidEnum() 307 | { 308 | $enumMap = new EnumMap('MabeEnumTest\TestAsset\EnumBasic'); 309 | 310 | $this->assertFalse($enumMap->contains(EnumInheritance::INHERITANCE())); 311 | $this->assertFalse($enumMap->contains(EnumInheritance::INHERITANCE)); 312 | 313 | $this->assertFalse(isset($enumMap[EnumInheritance::INHERITANCE()])); 314 | $this->assertFalse(isset($enumMap[EnumInheritance::INHERITANCE])); 315 | } 316 | } 317 | --------------------------------------------------------------------------------