├── .github └── workflows │ └── main.yml ├── .idea ├── EnumHelper.iml ├── encodings.xml ├── modules.xml ├── php.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── CasesIndexedByName.php ├── EnumRestorableFromName.php └── EnumValidatableCase.php /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: [ push, pull_request ] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | continue-on-error: ${{ matrix.php == '8.2' }} 7 | strategy: 8 | matrix: 9 | experimental: 10 | - false 11 | php-version: 12 | - '8.1' 13 | - '8.2' 14 | 15 | name: PHP ${{ matrix.php-version }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup PHP, with composer and extensions 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: ${{ matrix.php-version }} 25 | coverage: none 26 | 27 | - name: Get composer cache directory 28 | id: composer-cache 29 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 30 | 31 | - name: Cache composer dependencies 32 | uses: actions/cache@v2 33 | with: 34 | path: ${{ steps.composer-cache.outputs.dir }} 35 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: ${{ runner.os }}-composer- 37 | 38 | - name: Delete composer lock file 39 | id: composer-lock 40 | if: ${{ matrix.php-version == '8.1' }} 41 | run: | 42 | echo "::set-output name=flags::--ignore-platform-reqs" 43 | 44 | - name: Install dependencies 45 | run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} 46 | 47 | - name: Setup problem matchers for PHP 48 | run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" 49 | 50 | - name: Setup problem matchers for PHPUnit 51 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 52 | 53 | - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" 54 | env: 55 | FAILURE_ACTION: "${{ matrix.experimental == true }}" 56 | run: vendor/bin/phpunit --verbose || $FAILURE_ACTION 57 | 58 | phpcs: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v2 63 | 64 | - name: Setup PHP, with composer and extensions 65 | uses: shivammathur/setup-php@v2 66 | with: 67 | php-version: 8.1 68 | coverage: none 69 | tools: cs2pr 70 | 71 | - name: Get composer cache directory 72 | id: composer-cache 73 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 74 | 75 | - name: Cache composer dependencies 76 | uses: actions/cache@v2 77 | with: 78 | path: ${{ steps.composer-cache.outputs.dir }} 79 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 80 | restore-keys: ${{ runner.os }}-composer- 81 | 82 | - name: Install dependencies 83 | run: composer install --no-progress --prefer-dist --optimize-autoloader 84 | 85 | - name: Code style with PHP_CodeSniffer 86 | run: ./vendor/bin/phpcs -q --report=checkstyle src/ tests/src/ | cs2pr 87 | 88 | versions: 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Checkout 92 | uses: actions/checkout@v2 93 | 94 | - name: Setup PHP, with composer and extensions 95 | uses: shivammathur/setup-php@v2 96 | with: 97 | php-version: 8.1 98 | coverage: none 99 | tools: cs2pr 100 | 101 | - name: Get composer cache directory 102 | id: composer-cache 103 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 104 | 105 | - name: Cache composer dependencies 106 | uses: actions/cache@v2 107 | with: 108 | path: ${{ steps.composer-cache.outputs.dir }} 109 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 110 | restore-keys: ${{ runner.os }}-composer- 111 | 112 | - name: Install dependencies 113 | run: composer install --no-progress --prefer-dist --optimize-autoloader 114 | 115 | - name: Code Version Compatibility check with PHP_CodeSniffer 116 | run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full src/ tests/src/ --standard=PHPCompatibility --runtime-set testVersion 8.1- 117 | 118 | coverage: 119 | runs-on: ubuntu-latest 120 | steps: 121 | - name: Checkout 122 | uses: actions/checkout@v2 123 | 124 | - name: Setup PHP, with composer and extensions 125 | uses: shivammathur/setup-php@v2 126 | with: 127 | php-version: 8.1 128 | coverage: pcov 129 | 130 | - name: Get composer cache directory 131 | id: composer-cache 132 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 133 | 134 | - name: Cache composer dependencies 135 | uses: actions/cache@v2 136 | with: 137 | path: ${{ steps.composer-cache.outputs.dir }} 138 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 139 | restore-keys: ${{ runner.os }}-composer- 140 | 141 | - name: Install dependencies 142 | run: composer install --no-progress --prefer-dist --optimize-autoloader 143 | 144 | - name: Test Coverage 145 | run: ./vendor/bin/phpunit --verbose --coverage-text 146 | -------------------------------------------------------------------------------- /.idea/EnumHelper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `EnumHelper` will be documented in this file. 4 | 5 | ## 1.0.2 - 2022-01-20 6 | 7 | - Added `CasesIndexedByName` trait, providing a `casesIndexedByName()` method to return an associative array of cases with the case name as the index, rather than an enumerated array that is returned by `cases()`. 8 | 9 | ## 1.0.1 - 2021-12-08 10 | 11 | - Added `EnumValidatableCase` trait, providing an `isValidCase()` method to validate a string value against the set of case names defined for an enum. 12 | 13 | ## 1.0.0 - 2021-12-07 14 | 15 | - Initial release 16 | 17 | Created `EnumValidatableCase` trait, providing a `fromName()` method that allows a string value which matches an enum case to be used to create an instance of that enum. 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2017` `Mark Baker` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A library of helper traits for working with PHP 8.1 enums 2 | 3 | [![Build Status](https://github.com/MarkBaker/EnumHelper/workflows/main/badge.svg)](https://github.com/MarkBaker/EnumHelper/actions) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/markbaker/enumhelper)](https://packagist.org/packages/markbaker/enumhelper) 5 | [![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/EnumHelper)](https://packagist.org/packages/markbaker/enumhelper) 6 | [![License](https://img.shields.io/github/license/MarkBaker/EnumHelper)](https://packagist.org/packages/markbaker/enumhelper) 7 | 8 | This package provides a series of traits that allows you to: 9 | - RestorableFromName Trait 10 | 11 | Create/restore a PHP 8.1 enum from a name string. 12 | - EnumValidatableCase 13 | 14 | Validate an enum name from a name string. 15 | - CasesIndexedByName 16 | 17 | Similar to the standard static `cases()` method, but returns an associative array, indexed by the case names. 18 | 19 | ## Installation 20 | 21 | You can install the package via composer: 22 | 23 | ```bash 24 | composer require markbaker/enumhelper 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### RestorableFromName Trait 30 | 31 | In PHP 8.1, it is possible to create a backed enum from a value using the enum's `from()` or `tryFrom()` methods. 32 | 33 | ```php 34 | enum Suit: string { 35 | case Hearts = 'H'; 36 | case Diamonds = 'D'; 37 | case Clubs = 'C'; 38 | case Spades = 'S'; 39 | } 40 | 41 | $suit = Suit::Diamonds; 42 | 43 | $value = $suit->value; // Returns 'D' 44 | 45 | $newSuit = Suit::from($value); 46 | ``` 47 | 48 | The `EnumHelper\EnumRestorableFromName` trait provided in this library adds a `fromName()` method to any enum where you want to create an enum from its name, rather than from its value. 49 | 50 | ```php 51 | enum Suit: string { 52 | use EnumHelper\EnumRestorableFromName; 53 | 54 | case Hearts = 'H'; 55 | case Diamonds = 'D'; 56 | case Clubs = 'C'; 57 | case Spades = 'S'; 58 | } 59 | 60 | $suit = Suit::Diamonds; 61 | 62 | $suitName = $suit->name; // Returns 'Diamonds' 63 | 64 | $newSuit = Suit::fromName($suitName); 65 | ``` 66 | 67 | An invalid name will throw an exception. Note that names are case-sensitive. 68 | 69 | This could be useful if you wanted to store the name in a database for readability (particularly appropriate for unbacked enums); then recreate the enum in the model when you load the database record. 70 | 71 | This works with both backed and unbacked enums. 72 | 73 | ### EnumValidatableCase Trait 74 | 75 | Useful to validate if a name has been defined in the case set for an enum: 76 | 77 | ```php 78 | enum Suit: string { 79 | use EnumHelper\EnumValidatableCase; 80 | 81 | case Hearts = 'H'; 82 | case Diamonds = 'D'; 83 | case Clubs = 'C'; 84 | case Spades = 'S'; 85 | } 86 | 87 | $suit = Suit::Diamonds; 88 | 89 | $validCaseName = Suit::Hearts; 90 | $isCaseNameValid = Suit::isValidCase($validCaseName); // Returns boolean true 91 | 92 | $invalidCaseName = 'HeArTs'; 93 | $isCaseNameValid = Suit::isValidCase($invalidCaseName); // Returns boolean false 94 | ``` 95 | 96 | Note that names are case-sensitive. 97 | 98 | This works with both backed and unbacked enums. 99 | 100 | ### CasesIndexedByName Trait 101 | 102 | While PHP 8.1+ Enums already provide a standard static `cases()` method to return a list of all cases defined for that enum: 103 | ```php 104 | enum Suit: string { 105 | use EnumHelper\EnumValidatableCase; 106 | 107 | case Hearts = 'H'; 108 | case Diamonds = 'D'; 109 | case Clubs = 'C'; 110 | case Spades = 'S'; 111 | } 112 | 113 | var_dump(Suit::cases()); 114 | ``` 115 | which returns an enumerated array of the defined cases. 116 | ``` 117 | array(4) { 118 | [0]=> 119 | enum(Suit::Hearts) 120 | [1]=> 121 | enum(Suit::Diamonds) 122 | [2]=> 123 | enum(Suit::Clubs) 124 | [3]=> 125 | enum(Suit::Spades) 126 | } 127 | ``` 128 | Using the `CasesIndexedByName` Trait and the related `casesIndexedByName()` method 129 | 130 | ```php 131 | enum Suit: string { 132 | use EnumHelper\CasesIndexedByName; 133 | 134 | case Hearts = 'H'; 135 | case Diamonds = 'D'; 136 | case Clubs = 'C'; 137 | case Spades = 'S'; 138 | } 139 | 140 | var_dump(Suit::casesIndexedByName()); 141 | ``` 142 | which will return an associative array of the defined cases, where the array index is the case name. 143 | ``` 144 | array(4) { 145 | ["Hearts"]=> 146 | enum(Suit::Hearts) 147 | ["Diamonds"]=> 148 | enum(Suit::Diamonds) 149 | ["Clubs"]=> 150 | enum(Suit::Clubs) 151 | ["Spades"]=> 152 | enum(Suit::Spades) 153 | } 154 | ``` 155 | This can be particularly useful if you filter the `cases()` list to return a subset of cases, and don't like the gaps in numeric sequence in the enumerated array. 156 | 157 | ```php 158 | enum Suit: string { 159 | use EnumHelper\CasesIndexedByName; 160 | 161 | public const RED = 'Red'; 162 | public const BLACK = 'Black'; 163 | 164 | case Hearts = 'H'; 165 | case Diamonds = 'D'; 166 | case Clubs = 'C'; 167 | case Spades = 'S'; 168 | 169 | public function color(): string { 170 | return match($this) { 171 | self::Hearts, self::Diamonds => self::RED, 172 | self::Clubs, self::Spades => self::BLACK, 173 | }; 174 | } 175 | 176 | public static function red(): array { 177 | return array_filter( 178 | self::casesIndexedByName(), 179 | fn(self $suit) => $suit->color() === self::RED 180 | ); 181 | } 182 | 183 | public static function black(): array { 184 | return array_filter( 185 | self::casesIndexedByName(), 186 | fn(self $suit) => $suit->color() === self::BLACK 187 | ); 188 | } 189 | } 190 | 191 | var_dump(Suit::black()); 192 | ``` 193 | will return 194 | ``` 195 | array(2) { 196 | ["Clubs"]=> 197 | enum(Suit::Clubs) 198 | ["Spades"]=> 199 | enum(Suit::Spades) 200 | } 201 | ``` 202 | 203 | ## Changelog 204 | 205 | Please see the [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 206 | 207 | ## License 208 | 209 | This library is released under the MIT License (MIT). Please see [License File](LICENSE.md) for more information. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markbaker/enumhelper", 3 | "description": "A small library that provides Helper Traits for PHP 8.1 Enums", 4 | "keywords": ["enum", "helper", "trait"], 5 | "homepage": "https://github.com/MarkBaker/EnumHelper", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Mark Baker", 10 | "email": "mark@demon-angel.eu" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.1" 15 | }, 16 | "require-dev": { 17 | "squizlabs/php_codesniffer": "^3.4", 18 | "phpunit/phpunit": "^9.5||^10.0", 19 | "phpcompatibility/php-compatibility": "^9.0", 20 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "EnumHelper\\": "src/" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "EnumHelper\\TestData\\": "tests/data", 30 | "EnumHelper\\Tests\\": "tests/src" 31 | } 32 | }, 33 | "scripts": { 34 | "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full src/ tests/src/ -n", 35 | "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full src/ --runtime-set testVersion 8.1- -n" 36 | }, 37 | "config": { 38 | "sort-packages": true 39 | }, 40 | "minimum-stability": "dev", 41 | "prefer-stable": true 42 | } -------------------------------------------------------------------------------- /src/CasesIndexedByName.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | public static function casesIndexedByName() 11 | { 12 | return array_combine( 13 | array_map( 14 | fn(self $case) => $case->name, 15 | self::cases() 16 | ), 17 | self::cases() 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EnumRestorableFromName.php: -------------------------------------------------------------------------------- 1 | getCase($name); 12 | return $enumReflector->getValue(); 13 | } catch (\ReflectionException $e) { 14 | throw new \Exception(sprintf('Undefined enum name %s::%s', self::class, $name)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EnumValidatableCase.php: -------------------------------------------------------------------------------- 1 | getCase($name); 12 | } catch (\ReflectionException $e) { 13 | return false; 14 | } 15 | 16 | return true; 17 | } 18 | } 19 | --------------------------------------------------------------------------------