├── infection.json.dist ├── psalm.xml ├── phpunit.xml ├── composer.json ├── CHANGELOG.md ├── LICENSE.md ├── src └── Enum.php └── README.md /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ] 6 | }, 7 | "logs": { 8 | "text": "php:\/\/stderr", 9 | "badge": { 10 | "branch": "master" 11 | } 12 | }, 13 | "mutators": { 14 | "@default": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ./tests 21 | 22 | 23 | 24 | 25 | 26 | ./src 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vjik/php-enum", 3 | "description": "PHP Enum Implementation", 4 | "type": "library", 5 | "keywords": [ 6 | "php", 7 | "enum" 8 | ], 9 | "license": "BSD-3-Clause", 10 | "support": { 11 | "issues": "https://github.com/vjik/php-enum/issues", 12 | "source": "https://github.com/vjik/php-enum" 13 | }, 14 | "minimum-stability": "stable", 15 | "require": { 16 | "php": "^8.0" 17 | }, 18 | "require-dev": { 19 | "infection/infection": "^0.23.0", 20 | "phpunit/phpunit": "^9.4", 21 | "vimeo/psalm": "^4.7" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Vjik\\Enum\\": "src" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Vjik\\Enum\\Tests\\": "tests" 31 | } 32 | }, 33 | "config": { 34 | "sort-packages": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # PHP Enum Implementation Change Log 2 | 3 | ## 4.0.0 May 27, 2021 4 | 5 | Implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations): 6 | 7 | - New: Add protected method `match()`. 8 | - New: Add factory method `tryFrom()`. 9 | - New: Add method `getName()`. 10 | - Chg: Remove immutability objects. 11 | - Chg: Rename methods `toObjects()` to `cases()` and `toValues()` to `values()`. 12 | - Chg: Use private constants in enum object. 13 | - Chg: On create object via method `from()` with invalid value throws `ValueError` instead `UnexpectedValueException`. 14 | 15 | ## 3.0.0 May 26, 2021 16 | 17 | - Chg: Rewrite the package from scratch. 18 | 19 | ## 2.2.0 January 15, 2020 20 | 21 | - New: Add support static methods of current object in filters. 22 | 23 | ## 2.1.0 February 26, 2018 24 | 25 | - New: Add static method `get()` that returns object by its identifier. 26 | 27 | ## 2.0.0 September 4, 2017 28 | 29 | - Chg: Property `$value` renamed to `$id`. 30 | - Chg: Method `toValues()` renamed to `toIds()`. 31 | 32 | ## 1.2.0 August 29, 2017 33 | 34 | - New: Add support operator `in` in filters. 35 | 36 | ## 1.1.1 August 29, 2017 37 | 38 | - Bug: Fixed problem with values type casting to integer. 39 | 40 | ## 1.1.0 August 21, 2017 41 | 42 | - New: Add method `toObjects()`. 43 | 44 | ## 1.0.0 July 15, 2017 45 | 46 | - Initial release. 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2021 by Sergei Predvoditelev (https://predvoditelev.ru) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/Enum.php: -------------------------------------------------------------------------------- 1 | > 22 | */ 23 | private static array $cache = []; 24 | 25 | /** 26 | * @psalm-var array> 27 | */ 28 | private static array $instances = []; 29 | 30 | final protected function __construct(string $name, mixed $value) 31 | { 32 | $this->name = $name; 33 | $this->value = $value; 34 | } 35 | 36 | final public function getName(): string 37 | { 38 | return $this->name; 39 | } 40 | 41 | final public function getValue(): mixed 42 | { 43 | return $this->value; 44 | } 45 | 46 | final public function __toString(): string 47 | { 48 | return (string)$this->value; 49 | } 50 | 51 | /** 52 | * @return static 53 | */ 54 | final public static function from(mixed $value): self 55 | { 56 | $object = static::getInstanceByValue($value); 57 | if ($object === null) { 58 | throw new ValueError("Value '$value' is not part of the enum " . static::class . '.'); 59 | } 60 | 61 | return $object; 62 | } 63 | 64 | /** 65 | * @return static|null 66 | */ 67 | final public static function tryFrom(mixed $value): ?self 68 | { 69 | return static::getInstanceByValue($value); 70 | } 71 | 72 | /** 73 | * @return static 74 | */ 75 | final public static function __callStatic(string $name, array $arguments): self 76 | { 77 | $class = static::class; 78 | if (!isset(self::$instances[$class][$name])) { 79 | $enumValues = static::getEnumValues(); 80 | if (!array_key_exists($name, $enumValues)) { 81 | $message = "No static method or enum constant '$name' in class " . static::class . '.'; 82 | throw new BadMethodCallException($message); 83 | } 84 | self::$instances[$class][$name] = new static($name, $enumValues[$name]); 85 | } 86 | return self::$instances[$class][$name]; 87 | } 88 | 89 | final public static function values(): array 90 | { 91 | return array_values(self::getEnumValues()); 92 | } 93 | 94 | /** 95 | * @return static[] 96 | */ 97 | final public static function cases(): array 98 | { 99 | $class = static::class; 100 | 101 | $objects = []; 102 | /** @var mixed $value */ 103 | foreach (self::getEnumValues() as $key => $value) { 104 | if (!isset(self::$instances[$class][$key])) { 105 | self::$instances[$class][$key] = new static($key, $value); 106 | } 107 | $objects[] = self::$instances[$class][$key]; 108 | } 109 | 110 | return $objects; 111 | } 112 | 113 | final public static function isValid(mixed $value): bool 114 | { 115 | return in_array($value, static::getEnumValues(), true); 116 | } 117 | 118 | /** 119 | * @psalm-return array> 120 | */ 121 | protected static function data(): array 122 | { 123 | return []; 124 | } 125 | 126 | final protected function getPropertyValue(string $key, mixed $default = null): mixed 127 | { 128 | /** @psalm-suppress MixedArrayOffset */ 129 | return static::data()[$this->value][$key] ?? $default; 130 | } 131 | 132 | final protected function match(array $data, mixed $default = null): mixed 133 | { 134 | /** @psalm-suppress MixedArrayOffset */ 135 | return $data[$this->value] ?? $default; 136 | } 137 | 138 | /** 139 | * @return static|null 140 | */ 141 | private static function getInstanceByValue(mixed $value): ?self 142 | { 143 | $class = static::class; 144 | 145 | /** @var mixed $enumValue */ 146 | foreach (self::getEnumValues() as $key => $enumValue) { 147 | if ($enumValue === $value) { 148 | if (!isset(self::$instances[$class][$key])) { 149 | self::$instances[$class][$key] = new static($key, $value); 150 | } 151 | return self::$instances[$class][$key]; 152 | } 153 | } 154 | 155 | return null; 156 | } 157 | 158 | /** 159 | * @psalm-return array 160 | */ 161 | private static function getEnumValues(): array 162 | { 163 | $class = static::class; 164 | 165 | if (!isset(static::$cache[$class])) { 166 | /** @psalm-suppress TooManyArguments Remove this after fix https://github.com/vimeo/psalm/issues/5837 */ 167 | static::$cache[$class] = (new ReflectionClass($class))->getConstants(ReflectionClassConstant::IS_PRIVATE); 168 | } 169 | 170 | return static::$cache[$class]; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Enum Implementation 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/vjik/php-enum/v/stable.png)](https://packagist.org/packages/vjik/php-enum) 4 | [![Total Downloads](https://poser.pugx.org/vjik/php-enum/downloads.png)](https://packagist.org/packages/vjik/php-enum) 5 | [![Build status](https://github.com/vjik/php-enum/workflows/build/badge.svg)](https://github.com/vjik/php-enum/actions?query=workflow%3Abuild) 6 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fvjik%2Fphp-enum%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/vjik/php-enum/master) 7 | [![static analysis](https://github.com/vjik/php-enum/workflows/static%20analysis/badge.svg)](https://github.com/vjik/php-enum/actions?query=workflow%3A%22static+analysis%22) 8 | 9 | The package implement ideas from [RFC Enumerations](https://wiki.php.net/rfc/enumerations) and provide abstract class `Enum` that intended to create 10 | [enumerated objects](https://en.wikipedia.org/wiki/Enumerated_type) with support [extra data](#extradata) and auxiliary static functions [`values()`](#values), [`cases()`](#cases) and [`isValid()`](#isValid). 11 | 12 | ## Requirements 13 | 14 | - PHP 8.0 or higher. 15 | 16 | ## Installation 17 | 18 | The package could be installed with composer: 19 | 20 | ```shell 21 | composer require vjik/php-enum --prefer-dist 22 | ``` 23 | 24 | ## General usage 25 | 26 | ### Declaration of class 27 | 28 | ```php 29 | use Vjik\Enum\Enum; 30 | 31 | /** 32 | * @method static self NEW() 33 | * @method static self PROCESS() 34 | * @method static self DONE() 35 | */ 36 | final class Status extends Enum 37 | { 38 | private const NEW = 'new'; 39 | private const PROCESS = 'process'; 40 | private const DONE = 'done'; 41 | } 42 | ``` 43 | 44 | ### Creating an object 45 | 46 | #### By static method `from()` 47 | 48 | ```php 49 | $process = Status::from('process'); 50 | ``` 51 | 52 | On create object with invalid value throws `ValueError`. 53 | 54 | #### By static method `tryFrom()` 55 | 56 | ```php 57 | $process = Status::tryFrom('process'); // Status object with value "process" 58 | $process = Status::tryFrom('not-exists'); // null 59 | ``` 60 | 61 | On create object with invalid value returns `null`. 62 | 63 | #### By static method with a name identical to the constant name 64 | 65 | Static methods are automatically implemented to provide quick access to an enum value. 66 | 67 | ```php 68 | $process = Status::PROCESS(); 69 | ``` 70 | 71 | ### Getting value and name 72 | 73 | ```php 74 | Status::DONE()->getName(); // DONE 75 | Status::DONE()->getValue(); // done 76 | ``` 77 | 78 | ### Class with extra data 79 | 80 | Set data in the protected static function `data()` and create getters using the protected method `getPropertyValue()`. 81 | Also you can create getter using protected method `match()`. 82 | 83 | ```php 84 | use Vjik\Enum\Enum; 85 | 86 | /** 87 | * @method static self CREATE() 88 | * @method static self UPDATE() 89 | */ 90 | final class Action extends Enum 91 | { 92 | private const CREATE = 1; 93 | private const UPDATE = 2; 94 | 95 | protected static function data(): array 96 | { 97 | return [ 98 | self::CREATE => [ 99 | 'tip' => 'Create document', 100 | ], 101 | self::UPDATE => [ 102 | 'tip' => 'Update document', 103 | ], 104 | ]; 105 | } 106 | 107 | public function getTip(): string 108 | { 109 | /** @var string */ 110 | return $this->getPropertyValue('tip'); 111 | } 112 | 113 | public function getColor(): string 114 | { 115 | return $this->match([ 116 | self::CREATE => 'red', 117 | self::UPDATE => 'blue', 118 | ]); 119 | } 120 | 121 | public function getCode(): int 122 | { 123 | return $this->match([ 124 | self::CREATE => 1, 125 | ], 99); 126 | } 127 | } 128 | ``` 129 | 130 | Usage: 131 | 132 | ```php 133 | echo Action::CREATE()->getTip(); 134 | echo Action::CREATE()->getColor(); 135 | echo Action::CREATE()->getCode(); 136 | ``` 137 | 138 | ### Auxiliary static functions 139 | 140 | #### List of values `values()` 141 | 142 | Returns list of values. 143 | 144 | ```php 145 | // [1, 2] 146 | Action::values(); 147 | ``` 148 | 149 | #### List of objects `cases()` 150 | 151 | Returns list of objects: 152 | 153 | ```php 154 | // [$createObject, $updateObject] 155 | Action::cases(); 156 | ``` 157 | 158 | #### Validate value `isValid()` 159 | 160 | Check if value is valid on the enum set. 161 | 162 | ```php 163 | Action::isValid(1); // true 164 | Action::isValid(99); // false 165 | ``` 166 | 167 | ### Casting to string 168 | 169 | `Enum` support casting to string (using magic method `__toString`). The value is returned as a string. 170 | 171 | ```php 172 | echo Status::DONE(); // done 173 | ``` 174 | 175 | ## Testing 176 | 177 | ### Unit testing 178 | 179 | The package is tested with [PHPUnit](https://phpunit.de/). To run tests: 180 | 181 | ```shell 182 | ./vendor/bin/phpunit 183 | ``` 184 | 185 | ### Mutation testing 186 | 187 | The package tests are checked with [Infection](https://infection.github.io/) mutation framework. To run it: 188 | 189 | ```shell 190 | ./vendor/bin/infection 191 | ``` 192 | 193 | ### Static analysis 194 | 195 | The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis: 196 | 197 | ```shell 198 | ./vendor/bin/psalm 199 | ``` 200 | 201 | ## License 202 | 203 | The PHP Enum implementation is free software. It is released under the terms of the BSD License. Please see [`LICENSE`](./LICENSE.md) for more information. 204 | 205 | ## Credits 206 | 207 | Version 3 of this package is inspired by [`myclabs/php-enum`](https://github.com/myclabs/php-enum). 208 | --------------------------------------------------------------------------------