├── .github └── workflows │ └── pr_test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Enum.php └── Exceptions │ ├── DuplicateKeyException.php │ ├── EnumException.php │ ├── InvalidEnumException.php │ ├── InvalidKeyException.php │ └── InvalidValueException.php └── tests ├── Stub ├── Animal.php ├── Beverage.php ├── DuplicateKey.php ├── Fruit.php └── Number.php └── Unit ├── ComparisonTest.php ├── EnumTest.php ├── InstanceCreationTest.php ├── InstanceTest.php ├── KeyValueNameExchangeTest.php └── ValidationTest.php /.github/workflows/pr_test.yml: -------------------------------------------------------------------------------- 1 | name: PR Tests 2 | 3 | on: 4 | pull_request: 5 | types: [ synchronize, opened, reopened ] 6 | 7 | jobs: 8 | test: 9 | name: Run tests 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | php: ["7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"] 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Install dependencies 20 | run: | 21 | docker run -v $(pwd):/app php:${{ matrix.php }}-cli-alpine /bin/sh -c \ 22 | 'cd /app && curl -sS https://getcomposer.org/installer | php -- --filename=composer && php composer update --prefer-stable --prefer-dist --no-interaction && rm composer' 23 | 24 | - name: Run tests 25 | run: | 26 | docker run -v $(pwd):/app php:${{ matrix.php }}-cli-alpine /bin/sh -c 'cd /app && ./vendor/bin/phpunit' 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | /tests/report 4 | composer.lock 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.4.0 2 | 3 | - Add support for PHP 8.3, 8.4 4 | - Limit support explicitly to PHP 7.0-8.4 5 | 6 | # 2.3.0 7 | 8 | - Limit support explicitly to PHP 7.0-8.2 9 | 10 | # 2.2.0 11 | 12 | - Add `isNot`, `isAnyOf`, `isNoneOf` expressive methods 13 | 14 | # 2.1.0 15 | 16 | - Add Enum::instances method to load set of all instances 17 | 18 | # 2.0.2 19 | 20 | - Fix bug where const val=0 could not load instance 21 | 22 | # 2.0.1 23 | 24 | - Show class name in error when comparing enum with an invalid object. 25 | 26 | # 2.0.0 27 | 28 | This release is a significant overhaul of the existing API, and therefore introduces breaking changes. 29 | See the list of updates below, and consult the [README](./README.md) for examples and details of the new API. 30 | 31 | - Add Enum::instanceFromKey($key) 32 | - **Breaking** Change `$instance->identifier` to `$instance->name()` 33 | - **Breaking** Change `Enum::identifiers()` to `Enum::names()` 34 | - **Breaking** Change `Enum::getKeyForIdentfier()` to `Enum::keyForName()` 35 | - **Breaking** Change `Enum::valueFor()` to `Enum::valueForKey()` 36 | - Add `Enum::nameForKey()` to get the constant name for a given key 37 | - **Breaking** Change `Enum::exists()` to `Enum::isValidKey()` 38 | - **Breaking** Change `Enum::checkExists()` to `Enum::requireValidKey()` 39 | - Fix `$instance->key()` to handle non-string keys 40 | - Fix `$instance->is()` to handle non-string keys 41 | - Fix late-static binding in some methods which referred to `self::` 42 | - Add `Enum::instanceFromName($name)` to get an instance via name (alternative to Enum::NAME()) 43 | - Change implementation of `Enum::instanceFromKey($key)` to use array_search 44 | - **Breaking** Change: the default provided static `map()` method will return an array of constant keys mapped to `null`. 45 | Previously it returned an empty array `[]` when not overridden. In practice, this may not effect userland code. 46 | - **Breaking** Change: you can no longer provide a non-keyed array in an `map()` method implemented 47 | in your sub-class. This method should be used to map keys to values (if necessary). A default map() method is provided 48 | which maps keys to `null` values. 49 | - **Breaking** Change `Enum::fromValue($val)` has been renamed to `Enum::keyForValue()` 50 | - **Breaking** Change: removed `Enum::flip()` 51 | - **Breaking** Change `Enum::constantMap()` to `Enum::namesAndKeys()` 52 | - Updated README to reflect API changes 53 | - Add `Enum::valueForName($name)` for completeness 54 | 55 | # 1.1.0 56 | 57 | - Add flip() and fromValue() 58 | 59 | # 1.0.0 60 | 61 | - initial release 62 | 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015 ignace nyamagana butera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | > **Warning** 4 | > If you are using PHP 8.1 or higher, we recommend you use [native php enums](https://www.php.net/manual/en/language.enumerations.examples.php) in place of this package. 5 | > 6 | > We may release some maintenance patches but support for this package is otherwise being discontinued. 7 | > 8 | > Feel free to fork our code and adapt it to your needs. 9 | 10 | 11 | # Enum PHP Library 12 | 13 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 14 | [![Packagist](https://img.shields.io/packagist/v/rexlabs/enum.svg)](https://packagist.org/packages/rexlabs/enum) 15 | 16 | 17 | ## Overview 18 | 19 | This library provides an Enum / Enumeration implementation for PHP. 20 | 21 | ## Why use this library 22 | 23 | * Very simple to implement and use. 24 | * Complex can optionally be mapped by providing a `map()` method. 25 | * Allows type-hinting when passing enumerated values between methods and classes. 26 | 27 | ## Usage 28 | 29 | 30 | First create a new class that extends `\Rexlabs\Enum\Enum` and do the following; 31 | 32 | 1. Declare your constants 33 | 2. *Optional*: provide a `map()` method: 34 | 35 | ## Example 36 | 37 | ```php 38 | value() method 61 | public static function map(): array 62 | { 63 | return [ 64 | self::BRISBANE => ["state"=>"QLD", "population"=>""], 65 | self::MELBOURNE => ["state"=>"VIC", "population"=>"5m"], 66 | self::SYDNEY => ["state"=>"NSW", "population"=>"5m"], 67 | ]; 68 | } 69 | 70 | } 71 | 72 | // Static access 73 | echo City::BRISBANE; // "Brisbane" 74 | echo City::MELBOURNE; // "Melbourne" 75 | City::names(); // (array)["BRISBANE", "BRISBANE", "SYDNEY"] 76 | City::keys(); // (array)["Brisbane", "Melbourne", "Sydney"] 77 | City::keyForName('BRISBANE'); // "Brisbane" 78 | City::nameForKey('Melbourne'); // "MELBOURNE" 79 | City::isValidKey('Sydney'); // (boolean)true 80 | City::isValidKey('Paris'); // (boolean)false 81 | 82 | // Getting an instance - all return a City instance. 83 | $city = City::BRISBANE(); 84 | $city = City::instanceFromName('BRISBANE'); 85 | $city = City::instanceFromKey('Brisbane'); 86 | 87 | // Working with an instance 88 | $city->name(); // "BRISBANE" 89 | $city->key(); // "Brisbane" 90 | $city->value()['population']; // null - no value mapped 91 | $city->is(City::BRISBANE); // (boolean)true 92 | $city->is(City::BRISBANE()); // (boolean)true 93 | $city->is(City::SYDNEY()); // (boolean)false 94 | $city->isNot(City::SYDNEY()); // (boolean)true 95 | $city->isAnyOf([City::BRISBANE()]); // (boolean)true 96 | $city->isNoneOf([City::BRISBANE()]); // (boolean)false 97 | 98 | // Or ... 99 | City::SYDNEY()->key(); // "Sydney" 100 | City::SYDNEY()->value(); // (array)["state"=>"NSW", "population"=>"5m"] 101 | ``` 102 | 103 | ## Dependencies 104 | 105 | - PHP 7.0 or above. 106 | 107 | ## Installation 108 | 109 | To install in your project: 110 | 111 | ```bash 112 | composer require rexlabs/enum 113 | ``` 114 | 115 | ### Type-hinting 116 | 117 | Now you can type-hint your `Enum` object as a dependency: 118 | 119 | ```php 120 | key()} is located in {$city->value()["state"]}, population: {$city->value()["population"]}\n"; 123 | } 124 | 125 | // Get a new instance 126 | announceCity(City::SYDNEY()); // "Sydney is located in NSW, population: 5m" 127 | ``` 128 | 129 | 130 | ## Instance Methods 131 | 132 | Each instance of `Enum` provides the following methods: 133 | 134 | ### name() 135 | 136 | Returns the constant name. 137 | 138 | ```php 139 | $enum->name(); 140 | ``` 141 | 142 | ### key() 143 | 144 | Returns the value/key assigned to the constant in the `const MY_CONST = 'key'` declaration. 145 | 146 | ```php 147 | $enum->key(); 148 | ``` 149 | 150 | ### value() 151 | 152 | Returns the value (if-any) that is mapped (in the array returned by `map()`). 153 | If no value is mapped, then this method returns `null`. 154 | 155 | ```php 156 | $enum->value(); 157 | ``` 158 | 159 | ### is(Enum|string $compare) 160 | 161 | Returns true if this instance is the same as the given constant key or enumeration instance. 162 | 163 | ```php 164 | $enum->is(City::SYDNEY); // Compare to constant key 165 | $enum->is(City::SYDNEY()); // Compare to instance 166 | ``` 167 | 168 | ### __toString() 169 | 170 | The `__toString()` method is defined to return the constant name. 171 | 172 | ```php 173 | (string)City::SYDNEY(); // "SYDNEY" 174 | ``` 175 | 176 | 177 | ## Static Methods 178 | 179 | ### map() 180 | 181 | Returns an array which maps the constant keys to a value. 182 | This method can be optionally implemented in a sub-class. 183 | The default implementation returns an array of keys mapped to `null`. 184 | 185 | ### instances() 186 | 187 | Returns an array of Enum instances. 188 | 189 | ### keys() 190 | 191 | Returns an array of constant keys. 192 | 193 | ### values() 194 | 195 | Returns an array of values defined in `map()`. If `map()` is not implemented then an array of null values will 196 | be returned. 197 | 198 | ### names() 199 | 200 | Returns an array of all the constant names declared with the class. 201 | 202 | ### namesAndKeys() 203 | 204 | Returns an associative array of CONSTANT_NAME => key, for all the constant names declared within the class. 205 | 206 | ### keyForName(string $name) 207 | 208 | Returns the key for the given constant name. 209 | 210 | ### nameForKey(string $key) 211 | 212 | Returns the constant name for the given key (the inverse of `keyForName`). 213 | 214 | ### valueForKey(string $key) 215 | 216 | Returns the value (or null if not mapped) for the given key (as declared in the `map()` method). 217 | 218 | ### keyForValue(mixed $value) 219 | 220 | Returns the key for the given value (as declared in the `map()` method). 221 | 222 | > Note: If duplicate values are contained within the `map()` method then only the first key will be returned. 223 | 224 | ### instanceFromName($name) 225 | 226 | Create instance of this Enum from the given constant name. 227 | 228 | ### instanceFromKey($key) 229 | 230 | Create instance of this Enum from the given key. 231 | 232 | ### isValidKey(string $key) 233 | 234 | Returns true if the given key exists. 235 | 236 | ### isValidName(string $name) 237 | 238 | Returns true if the given constant name (case-sensitive) has been declared in the class. 239 | 240 | ### requireValidKey(string $key) 241 | 242 | Throws a `\Rexlabs\Enum\Exceptions\InvalidKeyException` if the given key does NOT exist. 243 | 244 | ## Tests 245 | 246 | To run tests: 247 | ```bash 248 | composer tests 249 | ``` 250 | 251 | To run coverage report: 252 | ```bash 253 | composer coverage 254 | ``` 255 | Coverage report is output to `./tests/report/index.html` 256 | 257 | ## Contributing 258 | 259 | Contributions are welcome, please submit a pull-request or create an issue. 260 | Your submitted code should be formatted using PSR-1/PSR-2 standards. 261 | 262 | ## About 263 | 264 | - Author: [Jodie Dunlop](https://github.com/jodiedunlop) 265 | - License: [MIT](LICENSE) 266 | - Copyright (c) 2018 Rex Software Pty Ltd 267 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rexlabs/enum", 3 | "description": "Enumeration (enum) implementation for PHP", 4 | "keywords": [ 5 | "enum", 6 | "enumeration", 7 | "library", 8 | "constants", 9 | "valuelist" 10 | ], 11 | "type": "library", 12 | "authors": [ 13 | { 14 | "name": "Jodie Dunlop", 15 | "email": "jodie.dunlop@rexlabs.com.au" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.0 <8.5" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^6.5.7|^7.5|^8.5" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Rexlabs\\Enum\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Rexlabs\\Enum\\Tests\\": "tests/" 32 | } 33 | }, 34 | "scripts": { 35 | "test": "phpunit", 36 | "tests": "phpunit --colors=always", 37 | "coverage": "phpunit --coverage-html ./tests/report" 38 | }, 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | 9 | ./src 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Enum.php: -------------------------------------------------------------------------------- 1 | 23 | * @copyright 2018 Rex Software Pty Ltd 24 | */ 25 | abstract class Enum 26 | { 27 | /** @var array Cache of constant name => key per class */ 28 | public static $namesToKeysMap = []; 29 | 30 | /** @var array Cache of key => value per class (sanitized version of what map() returns) */ 31 | public static $keysToValuesMap = []; 32 | 33 | /** @var string */ 34 | protected $name; 35 | 36 | /** @var mixed */ 37 | protected $key; 38 | 39 | /** @var mixed */ 40 | protected $value; 41 | 42 | /** 43 | * Enum constructor. 44 | * 45 | * @param string $name 46 | * @param mixed $key 47 | * @param mixed $value 48 | */ 49 | public function __construct(string $name, $key, $value) 50 | { 51 | $this->name = $name; 52 | $this->key = $key; 53 | $this->value = $value; 54 | } 55 | 56 | /** 57 | * Return the keys for the map 58 | * 59 | * @return array 60 | */ 61 | public static function keys(): array 62 | { 63 | return array_keys(static::cachedMap()); 64 | } 65 | 66 | /** 67 | * Return set of every instance 68 | * 69 | * @return static[] 70 | */ 71 | public static function instances(): array 72 | { 73 | return array_map(function ($key) { 74 | return self::instanceFromKey($key); 75 | }, self::keys()); 76 | } 77 | 78 | /** 79 | * Returns a cached version of the map 80 | * It not only ensures the array supplied by map() is indexed by key, 81 | * it allows map() to do any intensive one-off operations. 82 | * 83 | * @return array 84 | */ 85 | public static function cachedMap(): array 86 | { 87 | // Ensure the map is indexed by key 88 | $class = static::class; 89 | if (!isset(static::$keysToValuesMap[$class])) { 90 | static::$keysToValuesMap[$class] = static::map(); 91 | } 92 | 93 | return static::$keysToValuesMap[$class]; 94 | } 95 | 96 | /** 97 | * Returns an array of $key => $value. 98 | * If an empty array is returned, the declared const keys will be used. 99 | * 100 | * @return array 101 | */ 102 | public static function map(): array 103 | { 104 | return array_fill_keys(array_values(static::namesAndKeys()), null); 105 | } 106 | 107 | /** 108 | * Return the values 109 | * 110 | * @return array 111 | */ 112 | public static function values(): array 113 | { 114 | return array_values(static::cachedMap()); 115 | } 116 | 117 | /** 118 | * Return the constant names 119 | * Each constant declared in the class will be returned. 120 | * 121 | * @return array 122 | */ 123 | public static function names(): array 124 | { 125 | return array_keys(static::namesAndKeys()); 126 | } 127 | 128 | /** 129 | * Return a map of constant names and their associated key. 130 | * 131 | * @return array 132 | */ 133 | public static function namesAndKeys(): array 134 | { 135 | $class = static::class; 136 | if (!array_key_exists($class, static::$namesToKeysMap)) { 137 | try { 138 | static::$namesToKeysMap[$class] = (new ReflectionClass($class))->getConstants(); 139 | } catch (ReflectionException $e) { 140 | // Reflection exceptions should be "unchecked" as they pertain 141 | // to errors in code. 142 | throw new LogicException($e->getMessage(), $e->getCode(), $e); 143 | } 144 | } 145 | 146 | return static::$namesToKeysMap[$class]; 147 | } 148 | 149 | /** 150 | * Handle calls to class::SOME_CONSTANT() and returns a new instance of the class. 151 | * 152 | * @param string $name 153 | * @param array $arguments 154 | * 155 | * @return static 156 | * @throws InvalidKeyException 157 | * @throws InvalidEnumException 158 | */ 159 | public static function __callStatic($name, $arguments) 160 | { 161 | $key = static::keyForName($name); 162 | 163 | return new static($name, $key, static::valueForKey($key)); 164 | } 165 | 166 | /** 167 | * Get the key for the given constant name. 168 | * 169 | * @param string $name 170 | * 171 | * @return null|mixed|string 172 | * @throws InvalidEnumException 173 | */ 174 | public static function keyForName(string $name) 175 | { 176 | $key = static::namesAndKeys()[$name] ?? null; 177 | if ($key === null) { 178 | throw new InvalidEnumException("Invalid constant name: $name in " . static::class); 179 | } 180 | 181 | return $key; 182 | } 183 | 184 | /** 185 | * Get the value for a given key. 186 | * 187 | * @param mixed|int|string $key 188 | * 189 | * @return mixed|null 190 | * @throws InvalidKeyException 191 | */ 192 | public static function valueForKey($key) 193 | { 194 | static::requireValidKey($key); 195 | 196 | return static::cachedMap()[$key]; 197 | } 198 | 199 | /** 200 | * Get the value for a given constant name. 201 | * 202 | * @param string $name 203 | * 204 | * @return mixed|null 205 | * @throws InvalidEnumException 206 | */ 207 | public static function valueForName($name) 208 | { 209 | $key = static::keyForName($name); 210 | 211 | return static::cachedMap()[$key] ?? null; 212 | } 213 | 214 | /** 215 | * Get the constant name for a given key. 216 | * 217 | * @param mixed|int|string $key 218 | * 219 | * @return string 220 | * @throws InvalidKeyException 221 | */ 222 | public static function nameForKey($key): string 223 | { 224 | $matches = array_keys(static::namesAndKeys(), $key, true); 225 | $numMatches = count($matches); 226 | if (!$numMatches) { 227 | throw new InvalidKeyException("Invalid key: $key in " . static::class); 228 | } 229 | if ($numMatches > 1) { 230 | throw new DuplicateKeyException("Unable to resolve name for $key, duplicate matches in " . static::class); 231 | } 232 | return $matches[0]; 233 | } 234 | 235 | /** 236 | * Returns the key for a given value (an inverted search). 237 | * Since keys can be assigned the same value, only the first match will be 238 | * returned. 239 | * 240 | * @param mixed $value 241 | * @return mixed 242 | */ 243 | public static function keyForValue($value) 244 | { 245 | $key = array_search($value, static::cachedMap(), true); 246 | if ($key === false) { 247 | throw new InvalidValueException("Value '{$value}' not found in map for " . static::class); 248 | } 249 | 250 | return $key; 251 | } 252 | 253 | /** 254 | * Create instance of this Enum from the constant name. 255 | * This method is case-sensitive, meaning if you declare your constant 256 | * as const MY_CONST = '...', then you will need to provide 'MY_CONST' as 257 | * the argument. 258 | * 259 | * @param string $name 260 | * 261 | * @return static 262 | * @throws InvalidEnumException 263 | */ 264 | public static function instanceFromName($name): self 265 | { 266 | if (!array_key_exists($name, static::namesAndKeys())) { 267 | throw new InvalidEnumException(sprintf('Invalid constant name: %s in %s', $name, static::class)); 268 | } 269 | return static::{$name}(); 270 | } 271 | 272 | /** 273 | * Create instance of this Enum from the key. 274 | * 275 | * @param string|int $key 276 | * 277 | * @return static 278 | * @throws InvalidKeyException 279 | */ 280 | public static function instanceFromKey($key): self 281 | { 282 | $name = array_search($key, static::namesAndKeys(), true); 283 | if ($name === false) { 284 | throw new InvalidKeyException(sprintf('Invalid key: %s in %s', $key, static::class)); 285 | } 286 | return static::{$name}(); 287 | } 288 | 289 | /** 290 | * Determine if a key exists within the constant map 291 | * 292 | * @param mixed|string $key 293 | * 294 | * @return boolean 295 | */ 296 | public static function isValidKey($key): bool 297 | { 298 | return array_key_exists($key, static::cachedMap()); 299 | } 300 | 301 | /** 302 | * Check if the key exists or throw an exception 303 | * 304 | * @param mixed|string|int $key 305 | * 306 | * @throws InvalidKeyException 307 | */ 308 | public static function requireValidKey($key) 309 | { 310 | if (!static::isValidKey($key)) { 311 | throw new InvalidKeyException("Invalid key: $key in " . static::class); 312 | } 313 | } 314 | 315 | /** 316 | * Check if the given constant name is valid. 317 | * @param string $name 318 | * 319 | * @return bool 320 | */ 321 | public static function isValidName(string $name): bool 322 | { 323 | return array_key_exists($name, static::namesAndKeys()); 324 | } 325 | 326 | /** 327 | * Overloads the string behavior, to return the constant name. 328 | * Same as $instance->name(). 329 | * 330 | * @return string 331 | */ 332 | public function __toString() 333 | { 334 | return $this->name(); 335 | } 336 | 337 | /** 338 | * Returns the constant name. 339 | * 340 | * @return string 341 | */ 342 | public function name(): string 343 | { 344 | return $this->name; 345 | } 346 | 347 | /** 348 | * Returns the value assigned to the constant declaration. 349 | * 350 | * @return mixed|string|int 351 | */ 352 | public function key() 353 | { 354 | return $this->key; 355 | } 356 | 357 | /** 358 | * Returns the mapped value (null if no value is assigned) 359 | * 360 | * @return mixed|null 361 | */ 362 | public function value() 363 | { 364 | return $this->value; 365 | } 366 | 367 | /** 368 | * Returns true if this instance is equal to the given key or Enum instance. 369 | * 370 | * @param static|mixed $compare 371 | * 372 | * @return bool 373 | * @throws InvalidEnumException 374 | */ 375 | public function is($compare): bool 376 | { 377 | if ($compare instanceof self) { 378 | return $compare->name() === $this->name(); 379 | } 380 | 381 | if (is_scalar($compare)) { 382 | return $compare === $this->key(); 383 | } 384 | 385 | $given = is_object($compare) 386 | ? get_class($compare) . ' instance' 387 | : gettype($compare); 388 | 389 | throw new InvalidEnumException( 390 | 'Enum instance or key (scalar) expected but ' . $given . ' given.' 391 | ); 392 | } 393 | 394 | /** 395 | * Returns false if this instance is equal to the given key or Enum instance. 396 | * 397 | * @param static|mixed $compare 398 | * 399 | * @return bool 400 | * @throws InvalidEnumException 401 | */ 402 | public function isNot($compare): bool 403 | { 404 | return !$this->is($compare); 405 | } 406 | 407 | /** 408 | * Returns true if this instance exists in the list of given keys or Enum instances. 409 | * 410 | * @param static[]|mixed[] $compares 411 | * 412 | * @return bool 413 | * @throws InvalidEnumException 414 | */ 415 | public function isAnyOf(array $compares): bool 416 | { 417 | foreach($compares as $compare){ 418 | if( $this->is($compare) ){ 419 | return true; 420 | } 421 | } 422 | 423 | return false; 424 | } 425 | 426 | /** 427 | * Returns false if this instance exists in the list of given keys or Enum instances. 428 | * 429 | * @param static[]|mixed[] $compares 430 | * 431 | * @return bool 432 | * @throws InvalidEnumException 433 | */ 434 | public function isNoneOf(array $compares): bool 435 | { 436 | return !$this->isAnyOf($compares); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /src/Exceptions/DuplicateKeyException.php: -------------------------------------------------------------------------------- 1 | 'Kitty-cat', 28 | self::DOG => null, 29 | self::HORSE => null, 30 | self::PIGEON => ['you','filthy','animal'], 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Stub/Beverage.php: -------------------------------------------------------------------------------- 1 | 'Corona', 32 | self::RED_WINE => 'Red Wine', 33 | self::WHITE_WINE => 'White Wine', 34 | self::RUM => 'Bundaberg', 35 | self::BOURBON => 'Jack Daniels', 36 | self::SCOTCH => 'Lagavulin', 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Stub/DuplicateKey.php: -------------------------------------------------------------------------------- 1 | 'Apple', 30 | static::BANANA => 'Banana', 31 | static::CHERRY => 'Cherry', 32 | static::EGGPLANT => 'Eggplant', 33 | static::AUBERGINE => 'Eggplant', 34 | ]; 35 | } 36 | } -------------------------------------------------------------------------------- /tests/Stub/Number.php: -------------------------------------------------------------------------------- 1 | is(Fruit::APPLE())); 17 | self::assertTrue(Fruit::BANANA()->is(Fruit::BANANA())); 18 | self::assertFalse(Fruit::APPLE()->is(Fruit::BANANA())); 19 | 20 | // Mapped 21 | self::assertTrue(Animal::CAT()->is(Animal::CAT())); 22 | self::assertFalse(Animal::DOG()->is(Animal::HORSE())); 23 | self::assertFalse(Animal::CAT()->is(Animal::PIGEON())); 24 | 25 | // Mixed types 26 | self::assertFalse(Fruit::APPLE()->is(Animal::CAT())); 27 | 28 | // By constant key 29 | self::assertTrue(Fruit::APPLE()->is(Fruit::APPLE)); 30 | self::assertFalse(Fruit::APPLE()->is(Fruit::BANANA)); 31 | self::assertFalse(Fruit::APPLE()->is('_not_defined_')); 32 | 33 | // When key is not a string 34 | self::assertTrue(Number::TWENTY_FOUR()->is(24)); 35 | } 36 | 37 | public function test_is_not_comparison() 38 | { 39 | self::assertFalse(Fruit::APPLE()->isNot(Fruit::APPLE())); 40 | self::assertTrue(Fruit::APPLE()->isNot(Fruit::BANANA())); 41 | } 42 | 43 | public function test_is_any_of_comparison() 44 | { 45 | self::assertTrue(Fruit::APPLE()->isAnyOf([Fruit::APPLE(), Fruit::BANANA()])); 46 | self::assertFalse(Fruit::APPLE()->isAnyOf([Fruit::BANANA(), Fruit::CHERRY(), Fruit::EGGPLANT()])); 47 | } 48 | 49 | public function test_is_none_of_comparison() 50 | { 51 | self::assertFalse(Fruit::APPLE()->isNoneOf([Fruit::APPLE(), Fruit::BANANA()])); 52 | self::assertTrue(Fruit::APPLE()->isNoneOf([Fruit::BANANA(), Fruit::CHERRY(), Fruit::EGGPLANT()])); 53 | } 54 | 55 | public function test_comparing_enum_to_an_invalid_argument_throws_exception() 56 | { 57 | $this->expectException(InvalidEnumException::class); 58 | $this->expectExceptionMessage('Enum instance or key (scalar) expected but array given.'); 59 | self::assertTrue(Fruit::APPLE()->is([])); 60 | } 61 | 62 | public function test_comparing_enum_to_an_invalid_object_throws_exception() 63 | { 64 | $this->expectException(InvalidEnumException::class); 65 | $this->expectExceptionMessage('Enum instance or key (scalar) expected but stdClass instance given.'); 66 | self::assertTrue(Fruit::APPLE()->is((object) [])); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Unit/EnumTest.php: -------------------------------------------------------------------------------- 1 | key(); 78 | }, $instances); 79 | 80 | self::assertEquals([ 81 | Animal::CAT, 82 | Animal::DOG, 83 | Animal::HORSE, 84 | Animal::PIGEON, 85 | ], $keys); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Unit/InstanceCreationTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidEnumException::class); 26 | 27 | /** @noinspection PhpUndefinedMethodInspection */ 28 | Fruit::NON_EXISTENT(); 29 | } 30 | 31 | public function test_get_instance_via_name() 32 | { 33 | $fruit = Fruit::instanceFromName('BANANA'); 34 | self::assertInstanceOf(Fruit::class, $fruit); 35 | 36 | $animal = Animal::instanceFromName('CAT'); 37 | self::assertInstanceOf(Animal::class, $animal); 38 | 39 | $beverage = Beverage::instanceFromName('BREW'); 40 | self::assertInstanceOf(Beverage::class, $beverage); 41 | 42 | $this->expectException(InvalidEnumException::class); 43 | Animal::instanceFromName('cat'); // Case sensitive 44 | } 45 | 46 | public function test_get_instance_via_key() 47 | { 48 | $animal = Animal::instanceFromKey('kitty'); 49 | self::assertTrue($animal->is(Animal::CAT())); 50 | 51 | $beverage = Beverage::instanceFromKey(0); 52 | self::assertTrue($beverage->is(Beverage::BREW)); 53 | self::assertFalse($beverage->is(Beverage::RED_WINE)); 54 | } 55 | 56 | public function test_get_instance_via_invalid_key_throws_exception() 57 | { 58 | $this->expectException(InvalidKeyException::class); 59 | Animal::instanceFromKey('_invalid_key_'); 60 | } 61 | 62 | public function test_instantiate_with_invalid_name_throws_exception() 63 | { 64 | $this->expectException(InvalidEnumException::class); 65 | 66 | /** @noinspection PhpUndefinedMethodInspection */ 67 | Animal::INVALID_KEY(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Unit/InstanceTest.php: -------------------------------------------------------------------------------- 1 | name()); 15 | self::assertEquals('DOG', Animal::DOG()->name()); 16 | } 17 | 18 | public function test_can_get_key_from_instance() 19 | { 20 | self::assertEquals('apple', Fruit::APPLE()->key()); 21 | self::assertEquals('dog', Animal::DOG()->key()); 22 | } 23 | 24 | public function test_can_get_key_from_instance_with_int_keys() 25 | { 26 | self::assertEquals(10, Number::TEN()->key()); 27 | self::assertEquals(24, Number::TWENTY_FOUR()->key()); 28 | } 29 | 30 | public function test_can_get_value_from_instance() 31 | { 32 | self::assertEquals('Apple', Fruit::APPLE()->value()); 33 | self::assertEquals(null, Number::TWENTY_FOUR()->value()); 34 | self::assertEquals('Kitty-cat', Animal::CAT()->value()); 35 | self::assertEquals(null, Animal::HORSE()->value()); 36 | self::assertEquals(['you', 'filthy', 'animal'], Animal::PIGEON()->value()); 37 | } 38 | 39 | public function test_casting_enum_to_string_returns_name() 40 | { 41 | // TODO feels like this behaviour should be changed to cast to the "key" 42 | // would be a breaking change as enums often cast to string and save as 43 | // the "name" in the database. 44 | self::assertEquals(Fruit::APPLE()->name(), (string)Fruit::APPLE()); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Unit/KeyValueNameExchangeTest.php: -------------------------------------------------------------------------------- 1 | expectException(InvalidKeyException::class); 32 | Fruit::valueForKey('_does_not_exist_'); 33 | } 34 | 35 | public function test_can_get_value_for_name() 36 | { 37 | self::assertEquals('Apple', Fruit::valueForName('APPLE')); 38 | self::assertEquals(null, Number::valueForName('TWENTY_FOUR')); 39 | } 40 | 41 | public function test_value_for_invalid_name_throws_exception() 42 | { 43 | $this->expectException(InvalidEnumException::class); 44 | Fruit::valueForName('_does_not_exist_'); 45 | } 46 | 47 | public function test_can_get_name_for_key() 48 | { 49 | self::assertEquals(Fruit::APPLE()->name(), Fruit::nameForKey(Fruit::APPLE)); 50 | } 51 | 52 | public function test_get_name_for_invalid_key_throws_exception() 53 | { 54 | $this->expectException(InvalidKeyException::class); 55 | Fruit::nameForKey('_does_not_exist_'); 56 | } 57 | 58 | public function test_get_name_for_duplicate_key_throws_exception() 59 | { 60 | $this->expectException(DuplicateKeyException::class); 61 | DuplicateKey::nameForKey(DuplicateKey::FIRST); 62 | } 63 | 64 | public function test_get_key_by_value() 65 | { 66 | self::assertEquals(Beverage::BREW, Beverage::keyForValue('Corona')); 67 | self::assertEquals(Beverage::RUM, Beverage::keyForValue('Bundaberg')); 68 | } 69 | 70 | public function test_get_key_by_invalid_value_throws_exception() 71 | { 72 | $this->expectException(InvalidValueException::class); 73 | self::assertEquals(Beverage::BREW, Beverage::keyForValue('Water')); 74 | } 75 | 76 | public function test_get_key_by_duplicate_value_returns_first() 77 | { 78 | // Fruit::EGGPLANT and Fruit::AUBERGINE are both mapped to 'Eggplant' 79 | self::assertEquals(Fruit::EGGPLANT, Fruit::keyForValue('Eggplant')); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Unit/ValidationTest.php: -------------------------------------------------------------------------------- 1 |