├── .github ├── pull_request_template.md └── workflows │ └── tests.yml ├── .gitignore ├── .styleci.yml ├── Changelog.md ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── CastsEnums.php ├── Commands │ ├── EnumMakeCommand.php │ └── stubs │ │ ├── enum.boot.stub │ │ ├── enum.labels.stub │ │ └── enum.stub ├── EnumServiceProvider.php └── EnumsAreCompatibleWithLaravelForms.php └── tests ├── AAASmokeTest.php ├── DetectsEnumVersion.php ├── DynamicClassResolverTest.php ├── EnumAccessorTest.php ├── EnumMutatorTest.php ├── EnumToArrayTest.php ├── Models ├── Address.php ├── AddressStatus.php ├── AddressStatusResolver.php ├── AddressType.php ├── BillingRule.php ├── Client.php ├── Eloquent.php ├── EloquentType.php ├── EloquentTypeProxy.php ├── Extended │ └── Address.php ├── Order.php ├── OrderStatus.php ├── OrderStatusV2.php ├── OrderStatusVX.php ├── OrderV2.php ├── OrderVX.php ├── Visibility.php └── VisibilityTalk.php ├── Resolvers └── AddressTypeResolver.php └── TestCase.php /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please go through this checklist, it is mandatory to accept your PR 2 | 3 | - [ ] Include the summary of your change. 4 | - [ ] Make sure all the tests are passing 5 | - [ ] Make sure StyleCI is passing 6 | - [ ] In case you add new functionality, it must be covered by tests 7 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 10 9 | strategy: 10 | matrix: 11 | php: ['8.0', '8.1', '8.2', '8.3', '8.4'] 12 | laravel: ['8.75', '8.83', '9.0', '9.52', '10.0', '10.48', '11.0', '11.44', '12.0'] 13 | enum: ['2', '3', '4'] 14 | exclude: 15 | - php: '8.0' 16 | laravel: '10.0' 17 | - php: '8.0' 18 | laravel: '10.48' 19 | - php: '8.0' 20 | laravel: '11.0' 21 | - php: '8.0' 22 | laravel: '11.44' 23 | - php: '8.0' 24 | laravel: '12.0' 25 | - php: '8.1' 26 | laravel: '11.0' 27 | - php: '8.1' 28 | laravel: '11.44' 29 | - php: '8.1' 30 | laravel: '12.0' 31 | name: PHP ${{ matrix.php }} Laravel ${{ matrix.laravel }} Enum ${{ matrix.enum }} 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@master 35 | - name: Installing PHP 36 | uses: shivammathur/setup-php@master 37 | with: 38 | php-version: ${{ matrix.php }} 39 | extensions: mbstring, json, sqlite3 40 | tools: composer:v2 41 | - name: Lock Laravel Version 42 | run: | 43 | composer require "konekt/enum:${{ matrix.enum }}.*" --no-update -v 44 | composer require "laravel/framework:${{ matrix.laravel }}.*" --no-update -v 45 | - name: Testbench Version Adjustments 46 | run: | 47 | is_smaller_version() [[ $(echo -e "$1\n$2"|sort -V|head -1) != $2 ]] 48 | is_smaller_version "${{ matrix.laravel }}" "9.36" && composer req "orchestra/testbench-core:7.10.2" --no-update 49 | is_smaller_version "${{ matrix.laravel }}" "9.34" && composer req "orchestra/testbench-core:7.8.1" --no-update 50 | is_smaller_version "${{ matrix.laravel }}" "9.32" && composer req "orchestra/testbench-core:7.7.1" --no-update 51 | is_smaller_version "${{ matrix.laravel }}" "9.12" && composer req "orchestra/testbench-core:7.4.0" --no-update 52 | is_smaller_version "${{ matrix.laravel }}" "9.7" && composer req "orchestra/testbench-core:7.3.0" --no-update 53 | is_smaller_version "${{ matrix.laravel }}" "9.6" && composer req "orchestra/testbench-core:7.2.0" --no-update 54 | is_smaller_version "${{ matrix.laravel }}" "9.5" && composer req "orchestra/testbench-core:7.1.0" --no-update || true 55 | - name: Composer Install 56 | run: composer install --prefer-dist --no-progress --no-interaction 57 | - name: Create Database 58 | run: mkdir -p database && touch database/database.sqlite 59 | - name: Run Tests 60 | run: php vendor/bin/phpunit --testdox 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea/ 4 | /.phpunit.result.cache 5 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr12 2 | risky: true 3 | enabled: 4 | - align_phpdoc 5 | - alpha_ordered_imports 6 | - cast_spaces 7 | - clean_namespace 8 | - declare_strict_types 9 | - fully_qualified_strict_types 10 | - implode_call 11 | - no_extra_block_blank_lines 12 | - no_extra_consecutive_blank_lines 13 | - no_spaces_inside_offset 14 | - no_spaces_outside_offset 15 | - no_whitespace_before_comma_in_array 16 | - no_unneeded_curly_braces 17 | - no_unneeded_control_parentheses 18 | - no_unneeded_final_method 19 | - no_unused_imports 20 | - no_unused_lambda_imports 21 | - no_useless_sprintf 22 | - normalize_index_brace 23 | - object_operator_without_whitespace 24 | - ordered_class_elements 25 | - print_to_echo 26 | - property_separation 27 | - short_array_syntax 28 | - short_list_syntax 29 | - standardize_not_equals 30 | - unalign_equals 31 | - unalign_double_arrow 32 | - yoda_style 33 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ### Konekt Enum Eloquent 3 | 4 | ## 1.10.0 5 | ##### 2025-03-03 6 | 7 | - Added Laravel 12 support 8 | - Added PHP 8.4 support 9 | 10 | ## 1.9.3 11 | ##### 2024-01-08 12 | 13 | - Added Laravel 11 support (experimental, pre-release) 14 | 15 | ## 1.9.2 16 | ##### 2023-11-17 17 | 18 | - Added PHP 8.3 support 19 | 20 | ## 1.9.1 21 | ##### 2023-09-25 22 | 23 | - Bumped minimal Laravel version to 8.75 24 | - Fixed issue where CastEnum did not take in account hidden or visible properties from Eloquent 25 | 26 | ## 1.9.0 27 | ##### 2023-02-16 28 | 29 | - Added Laravel 10 support 30 | - Added PHP 8.2 support 31 | 32 | ## 1.8.1 33 | ##### 2022-03-11 34 | 35 | - Fixed casting behavior so that other mutators defined on field are also 36 | being called before passing the value to the enum constructor 37 | 38 | ## 1.8.0 39 | ##### 2022-03-10 40 | 41 | - Added Enum v4 support 42 | 43 | ## 1.7.2 44 | ##### 2022-02-09 45 | 46 | - Tiny composer.json change for final Laravel 9.0 (removed minimum-stability: dev) 47 | 48 | ## 1.7.1 49 | ##### 2022-01-27 50 | 51 | - Added PHP 8.1 support 52 | - Added Laravel 9 support 53 | - Dropped PHP 7.3 and PHP 7.4 support 54 | - Dropped Laravel 6 and Laravel 7 support 55 | - Changed CI from Travis to Github Actions 56 | - Changed coding style to PSR-12 and using strict types across all classes 57 | 58 | ## 1.7.0 59 | ##### 2020-11-28 60 | 61 | - Dropped PHP 7.1 & PHP 7.2 support 62 | - Dropped Laravel 5 support 63 | - Added PHP 8 support 64 | 65 | ## 1.6.1 66 | ##### 2020-11-04 67 | 68 | - Fixed to array conversion exception when the enum field is not actually present in the model 69 | - Test suite fixes for Composer v2 compatibility (dropped Ocramius Package Manager dev requirement) 70 | - Various test suite improvements 71 | 72 | ## 1.6.0 73 | ##### 2020-09-12 74 | 75 | - Added Laravel 8 support 76 | 77 | ## 1.5.0 78 | ##### 2020-03-06 79 | 80 | - Laravel 7 and PHP 7.4 support 81 | 82 | ## 1.4.1 83 | ##### 2019-11-24 84 | 85 | - PhpUnit compatibility fix (affects dev mode only) 86 | 87 | ## 1.4.0 88 | ##### 2019-11-03 89 | 90 | - Added support for Eloquent `toArray()` (enums field values are properly included) 91 | 92 | ## 1.3.1 93 | ##### 2019-09-08 94 | 95 | - Fixed Laravel 6.0 Support 96 | 97 | ## 1.3.0 98 | ##### 2019-09-08 99 | 100 | - Added make:enum commands (when using in a Laravel application) 101 | - Enum 3.0.0 is supported 102 | - Dropped PHP 7.0 Support 103 | - Added Laravel 6.0 Support 104 | - Updated references to https://konekt.dev/enum website 105 | 106 | ## 1.2.0 107 | ##### 2018-11-04 108 | 109 | - Added Laravel Collective Forms compatibility (optional) 110 | - `isEnumAttribute()` method became protected (was private before) 111 | - Laravel 5.6 & 5.7 are supported 112 | 113 | ## 1.1.3 114 | ##### 2017-12-08 115 | 116 | - Fixed namespace creep bug with @notation on extended classes 117 | - PHP 7.2 is supported (Laravel 5.4 & 5.5) 118 | 119 | ## 1.1.2 120 | ##### 2017-11-24 121 | 122 | - Fixed Laravel 5.0 & 5.1 incompatibility introduced with v1.1.1 123 | 124 | ## 1.1.1 125 | ##### 2017-11-24 126 | 127 | - Fixed bug with `ClassName@method` notation resolution when the host 128 | model's base class name was present in the namespace. 129 | 130 | ## 1.1.0 131 | ##### 2017-10-16 132 | 133 | - Added support for `ClassName@method` notation 134 | - Travis tests PHP 7 with Laravel 5.0-5.5 & PHP7.1 with Laravel 5.4-5.5 135 | 136 | ## 1.0.0 137 | ##### 2017-10-06 138 | 139 | - Initial release for Enum 2.0+, Eloquent 5.0 - 5.5 140 | - Works like Eloquent's `$cast` property on models, but internally hooks into the mutator/accessor mechanism 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Attila Fulop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Konekt Enum Eloquent Bindings 2 | 3 | 4 | [![Tests](https://img.shields.io/github/actions/workflow/status/artkonekt/enum-eloquent/tests.yml?branch=master&style=flat-square)](https://github.com/artkonekt/enum-eloquent/actions?query=workflow%3Atests) 5 | [![Packagist Stable Version](https://img.shields.io/packagist/v/konekt/enum-eloquent.svg?style=flat-square&label=stable)](https://packagist.org/packages/konekt/enum-eloquent) 6 | [![Packagist downloads](https://img.shields.io/packagist/dt/konekt/enum-eloquent.svg?style=flat-square)](https://packagist.org/packages/konekt/enum-eloquent) 7 | [![StyleCI](https://styleci.io/repos/105900484/shield?branch=master)](https://styleci.io/repos/105900484) 8 | [![MIT Software License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 9 | 10 | This package provides support for auto casting [konekt enum](https://konekt.dev/enum) fields in [Eloquent models](https://laravel.com/docs/9.x/eloquent-mutators). 11 | 12 | > Supported Konekt Enum versions are 2.x, 3.x and 4.x with Eloquent (Laravel) 8 - 12 13 | 14 | [Changelog](Changelog.md) 15 | 16 | ## Installation 17 | 18 | `composer require konekt/enum-eloquent` 19 | 20 | ## Usage 21 | 22 | 1. Add the `CastsEnums` trait to your model 23 | 2. Define the attributes to be casted via the `protected $enums` property on the model 24 | 25 | ### Example 26 | 27 | **The Enum:** 28 | 29 | ```php 30 | namespace App; 31 | 32 | use Konekt\Enum\Enum; 33 | 34 | class OrderStatus extends Enum 35 | { 36 | const __DEFAULT = self::PENDING; 37 | // const __default = self::PENDING; // usage of default in v2.x 38 | 39 | const PENDING = 'pending'; 40 | const CANCELLED = 'cancelled'; 41 | const COMPLETED = 'completed'; 42 | 43 | } 44 | ``` 45 | 46 | **The Model:** 47 | 48 | ```php 49 | namespace App; 50 | 51 | use Illuminate\Database\Eloquent\Model; 52 | use Konekt\Enum\Eloquent\CastsEnums; 53 | 54 | class Order extends Model 55 | { 56 | use CastsEnums; 57 | 58 | protected $enums = [ 59 | 'status' => OrderStatus::class 60 | ]; 61 | } 62 | ``` 63 | 64 | **Client code:** 65 | ```php 66 | $order = Order::create([ 67 | 'status' => 'pending' 68 | ]); 69 | 70 | // The status attribute will be an enum object: 71 | echo get_class($order->status); 72 | // output: App\OrderStatus 73 | 74 | echo $order->status->value(); 75 | // output: 'pending' 76 | 77 | echo $order->status->isPending() ? 'yes' : 'no'; 78 | // output: yes 79 | 80 | echo $order->status->isCancelled() ? 'yes' : 'no'; 81 | // output: no 82 | 83 | // You can assign an enum object as attribute value: 84 | $order->status = OrderStatus::COMPLETED(); 85 | echo $order->status->value(); 86 | // output: 'completed' 87 | 88 | // It also works with mass assignment: 89 | $order = Order::create([ 90 | 'status' => OrderStatus::COMPLETED() 91 | ]); 92 | 93 | echo $order->status->value(); 94 | // output 'completed' 95 | 96 | // It still accepts scalar values: 97 | $order->status = 'completed'; 98 | echo $order->status->isCompleted() ? 'yes' : 'no'; 99 | // output: yes 100 | 101 | // But it doesn't accept scalar values that aren't in the enum: 102 | $order->status = 'negotiating'; 103 | // throws UnexpectedValueException 104 | // Given value (negotiating) is not in enum `App\OrderStatus` 105 | ``` 106 | 107 | ### Resolving Enum Class Runtime 108 | 109 | It is possible to defer the resolution of an Enum class to runtime. 110 | 111 | It happens using the `ClassName@method` notation known from Laravel. 112 | 113 | This is useful for libraries, so you can 'late-bind' the actual enum class and let the user to extend it. 114 | 115 | #### Example 116 | 117 | **The Model:** 118 | 119 | ```php 120 | namespace App; 121 | 122 | use Illuminate\Database\Eloquent\Model; 123 | use Konekt\Enum\Eloquent\CastsEnums; 124 | 125 | class Order extends Model 126 | { 127 | use CastsEnums; 128 | 129 | protected $enums = [ 130 | 'status' => 'OrderStatusResolver@enumClass' 131 | ]; 132 | } 133 | ``` 134 | 135 | **The Resolver:** 136 | 137 | ```php 138 | namespace App; 139 | 140 | class OrderStatusResolver 141 | { 142 | /** 143 | * Returns the enum class to use as order status enum 144 | * 145 | * @return string 146 | */ 147 | public static function enumClass() 148 | { 149 | return config('app.order.status.class', OrderStatus::class); 150 | } 151 | } 152 | ``` 153 | 154 | This way the enum class becomes configurable without the need to modify the Model code. 155 | 156 | ## Laravel Collective Forms Compatibility 157 | 158 | Laravel Collective [Forms Package](https://laravelcollective.com/docs/master/html) provides the 159 | `Form` facade known from Laravel v4.x. 160 | 161 | In case you want to use the Forms package with this one, you need to add the 162 | `EnumsAreCompatibleWithLaravelForms` trait to your model, next to `CastsEnums`. 163 | 164 | This will fix a problem where the forms package detects the enum label instead of its actual value 165 | as the value of the field. 166 | 167 | It is being done by adding the (undocumented) `getFormValue()` method to the model, that is being 168 | used by the forms library to obtain form field value. 169 | 170 | --- 171 | 172 | Enjoy! 173 | 174 | For detailed usage of konekt enums refer to the [Konekt Enum Documentation](https://konekt.dev/enum). 175 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "konekt/enum-eloquent", 3 | "description": "Enum attribute casting for Eloquent models", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": ["enum", "konekt", "artkonekt", "laravel", "eloquent"], 7 | "minimum-stability": "dev", 8 | "prefer-stable": true, 9 | "support": { 10 | "source": "https://github.com/artkonekt/enum-eloquent" 11 | }, 12 | "authors": [ 13 | { 14 | "name": "Attila Fulop", 15 | "homepage": "https://github.com/fulopattila122" 16 | }, 17 | { 18 | "name": "Semyon Chetvertnyh", 19 | "homepage": "https://github.com/semyonchetvertnyh" 20 | }, 21 | { 22 | "name": "Mark Boessenkool", 23 | "homepage": "https://github.com/TheM1984" 24 | } 25 | ], 26 | "require": { 27 | "php": "^8.0", 28 | "konekt/enum": "^2.0.2 || ^3.0 | ^4.0", 29 | "illuminate/database": "^8.75|9.*|10.*|11.*|12.*" 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "9 - 11", 33 | "illuminate/events": "^8.75|9.*|10.*|11.*|12.*" 34 | }, 35 | "autoload": { 36 | "psr-4": { "Konekt\\Enum\\Eloquent\\": "src/" } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Konekt\\Enum\\Eloquent\\Tests\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "Konekt\\Enum\\Eloquent\\EnumServiceProvider" 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/CastsEnums.php: -------------------------------------------------------------------------------- 1 | isEnumAttribute($key)) { 31 | $class = $this->getEnumClass($key); 32 | 33 | return $class::create(parent::getAttributeValue($key)); 34 | } 35 | 36 | return parent::getAttributeValue($key); 37 | } 38 | 39 | /** 40 | * Get an attribute from the model. 41 | * 42 | * @param string $key 43 | * @return mixed 44 | */ 45 | public function getAttribute($key) 46 | { 47 | if ($this->isEnumAttribute($key)) { 48 | return $this->getAttributeValue($key); 49 | } 50 | 51 | return parent::getAttribute($key); 52 | } 53 | 54 | /** 55 | * Set a given attribute on the model. 56 | * 57 | * @param string $key 58 | * @param mixed $value 59 | * @return $this 60 | */ 61 | public function setAttribute($key, $value) 62 | { 63 | if ($this->isEnumAttribute($key)) { 64 | $enumClass = $this->getEnumClass($key); 65 | if (!$value instanceof $enumClass) { 66 | $value = new $enumClass($value); 67 | } 68 | 69 | $this->attributes[$key] = $value->value(); 70 | 71 | return $this; 72 | } 73 | 74 | parent::setAttribute($key, $value); 75 | } 76 | 77 | /** 78 | * Convert the model's attributes to an array. 79 | * 80 | * @return array 81 | */ 82 | public function attributesToArray() 83 | { 84 | return $this->getArrayableItems( 85 | $this->addEnumAttributesToArray( 86 | parent::attributesToArray() 87 | ) 88 | ); 89 | } 90 | 91 | /** 92 | * Returns whether the attribute was marked as enum 93 | * 94 | * @param $key 95 | * 96 | * @return bool 97 | */ 98 | protected function isEnumAttribute($key) 99 | { 100 | return isset($this->enums[$key]); 101 | } 102 | 103 | protected function addEnumAttributesToArray(array $attributes): array 104 | { 105 | foreach ($this->enums as $key => $value) { 106 | // Don't set if the field is not present (pluck or not selecting them in the SQL can cause it) 107 | if (isset($this->attributes[$key])) { 108 | $attributes[$key] = $this->getAttributeValue($key)->value(); 109 | } 110 | } 111 | 112 | return $attributes; 113 | } 114 | 115 | /** 116 | * Returns the enum class. Supports 'FQCN\Class@method()' notation 117 | * 118 | * @param $key 119 | * 120 | * @return mixed 121 | */ 122 | private function getEnumClass($key) 123 | { 124 | $result = $this->enums[$key]; 125 | if (strpos($result, '@')) { 126 | $class = Str::before($result, '@'); 127 | $method = Str::after($result, '@'); 128 | 129 | // If no namespace was set, prepend the Model's namespace to the 130 | // class that resolves the enum class. Prevent this behavior, 131 | // by setting the resolver class with a leading backslash 132 | if (class_basename($class) == $class) { 133 | $class = 134 | Str::replaceLast( 135 | class_basename(get_class($this)), 136 | $class, 137 | self::class 138 | ); 139 | } 140 | 141 | $result = $class::$method(); 142 | } 143 | 144 | return $result; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Commands/EnumMakeCommand.php: -------------------------------------------------------------------------------- 1 | option('labels')) { 41 | return __DIR__ . '/stubs/enum.labels.stub'; 42 | } 43 | 44 | if ($this->option('boot')) { 45 | return __DIR__ . '/stubs/enum.boot.stub'; 46 | } 47 | 48 | return __DIR__ . '/stubs/enum.stub'; 49 | } 50 | 51 | /** 52 | * Get the default namespace for the class. 53 | * 54 | * @param string $rootNamespace 55 | * @return string 56 | */ 57 | protected function getDefaultNamespace($rootNamespace) 58 | { 59 | return $rootNamespace . '\Enums'; 60 | } 61 | 62 | /** 63 | * Get the console command options. 64 | * 65 | * @return array 66 | */ 67 | protected function getOptions() 68 | { 69 | return [ 70 | ['labels', 'l', InputOption::VALUE_NONE, 'Create an Enum class with predefined labels'], 71 | 72 | ['boot', 'b', InputOption::VALUE_NONE, 'Create an Enum class with [boot] method'], 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Commands/stubs/enum.boot.stub: -------------------------------------------------------------------------------- 1 | __('Option #1'), 21 | self::OPTION_TWO => __('Option #2'), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Commands/stubs/enum.labels.stub: -------------------------------------------------------------------------------- 1 | 'Option #1', 17 | self::OPTION_TWO => 'Option #2', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /src/Commands/stubs/enum.stub: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 19 | $this->commands([ 20 | Commands\EnumMakeCommand::class, 21 | ]); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/EnumsAreCompatibleWithLaravelForms.php: -------------------------------------------------------------------------------- 1 | isEnumAttribute($key)) { 22 | return $this->{$key}->value(); 23 | } 24 | 25 | return $this->{$key}; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/AAASmokeTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 27 | } 28 | 29 | /** 30 | * Test for minimum PHP version 31 | * 32 | * @depends smoke 33 | * @test 34 | */ 35 | public function php_version_satisfies_requirements() 36 | { 37 | $this->assertFalse( 38 | version_compare(PHP_VERSION, self::MIN_PHP_VERSION, '<'), 39 | 'PHP version ' . self::MIN_PHP_VERSION . ' or greater is required but only ' 40 | . PHP_VERSION . ' found.' 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/DetectsEnumVersion.php: -------------------------------------------------------------------------------- 1 | getEnumVersion()); 33 | 34 | return (int) $parts[0]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/DynamicClassResolverTest.php: -------------------------------------------------------------------------------- 1 | AddressType::SHIPPING, 33 | 'address' => 'Richard Avenue 33' 34 | ]); 35 | 36 | $this->assertInstanceOf(AddressType::class, $address->type); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function it_resolves_local_enum_class_name_from_the_at_notation() 43 | { 44 | $address = Address::create([ 45 | 'type' => AddressType::SHIPPING, 46 | 'address' => 'Richard Avenue 33' 47 | ]); 48 | 49 | $this->assertInstanceOf(AddressStatus::class, $address->status); 50 | } 51 | 52 | /** 53 | * @test 54 | */ 55 | public function at_notation_does_not_collide_if_class_name_is_in_nampespace() 56 | { 57 | $eloquent = Eloquent::create([]); 58 | 59 | $this->assertInstanceOf(EloquentType::class, $eloquent->type); 60 | } 61 | 62 | /** 63 | * @test 64 | */ 65 | public function it_keeps_original_namespace_with_at_notation_when_using_short_classnames_in_extended_classes() 66 | { 67 | $address = ExtendedAddress::create([ 68 | 'type' => AddressType::BILLING, 69 | 'address' => 'Alexander Platz 1.' 70 | ]); 71 | 72 | $this->assertInstanceOf(AddressStatus::class, $address->status); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/EnumAccessorTest.php: -------------------------------------------------------------------------------- 1 | 'SXCJ7WA', 37 | 'status' => OrderStatus::SUBMITTED 38 | ]); 39 | 40 | $this->assertNotNull($order->id); 41 | $this->assertInstanceOf(OrderStatus::class, $order->status); 42 | } 43 | 44 | /** 45 | * @test 46 | */ 47 | public function it_returns_the_enum_default_when_attribute_is_null() 48 | { 49 | // don't test if mayor version is lower than 3 50 | if ($this->getEnumVersionMajor() < 3) { 51 | $this->assertTrue(true); 52 | 53 | return; 54 | } 55 | 56 | $order = new Order([ 57 | 'number' => 'PLGU7S5' 58 | ]); 59 | 60 | $this->assertInstanceOf(OrderStatus::class, $order->status); 61 | $this->assertEquals(OrderStatus::__DEFAULT, $order->status->value()); 62 | } 63 | 64 | /** 65 | * @test 66 | */ 67 | public function it_returns_the_enum_v2_default_when_attribute_is_null() 68 | { 69 | // don't test if mayor version is 3 or higher 70 | if ($this->getEnumVersionMajor() >= 3) { 71 | $this->assertTrue(true); 72 | 73 | return; 74 | } 75 | 76 | $order = new OrderV2([ 77 | 'number' => 'PLGU7S5' 78 | ]); 79 | 80 | $this->assertInstanceOf(OrderStatusV2::class, $order->status); 81 | $this->assertEquals(OrderStatusV2::__default, $order->status->value()); 82 | } 83 | 84 | /** 85 | * @test 86 | */ 87 | public function it_returns_the_enum_backwards_compatible_default_when_attribute_is_null() 88 | { 89 | $order = new OrderVX([ 90 | 'number' => 'PLGU7S5' 91 | ]); 92 | 93 | $this->assertInstanceOf(OrderStatusVX::class, $order->status); 94 | $this->assertEquals(OrderStatusVX::__DEFAULT, $order->status->value()); 95 | $this->assertEquals(OrderStatusVX::__default, $order->status->value()); 96 | } 97 | 98 | /** 99 | * @test 100 | */ 101 | public function it_can_still_read_basic_properties_as_well() 102 | { 103 | $order = Order::create([ 104 | 'number' => 'UIH6GQS', 105 | 'status' => OrderStatus::SUBMITTED 106 | ]); 107 | 108 | $this->assertNotNull($order->id); 109 | $this->assertEquals('UIH6GQS', $order->number); 110 | } 111 | 112 | /** 113 | * @test 114 | */ 115 | public function it_can_still_read_casted_fields() 116 | { 117 | $order = Order::create([ 118 | 'number' => 'KH8FRWAD', 119 | 'status' => OrderStatus::PROCESSING, 120 | 'is_active' => 1 121 | ]); 122 | 123 | $this->assertNotNull($order->id); 124 | $this->assertInstanceOf(\DateTime::class, $order->created_at); 125 | $this->assertIsBool($order->is_active); 126 | } 127 | 128 | /** 129 | * @test 130 | */ 131 | public function it_doesnt_break_related_properties() 132 | { 133 | $client = Client::create(['name' => 'Britney Spears']); 134 | 135 | $order = Order::create([ 136 | 'number' => 'LDYG4G4', 137 | 'status' => OrderStatus::PROCESSING, 138 | 'client_id' => $client->id 139 | ]); 140 | 141 | $this->assertInstanceOf(Client::class, $order->client); 142 | $this->assertEquals($client->id, $order->client->id); 143 | } 144 | 145 | /** @test */ 146 | public function it_works_with_integer_database_fields() 147 | { 148 | $clientAny = Client::create(['name' => 'Pawel Jedrzejewsky']); 149 | $clientInvoiceOnly = Client::create(['name' => 'Pawel Jedrzejewsky', 'billing_rule' => 1]); 150 | $clientNoInvoice = Client::create(['name' => 'Pawel Jedrzejewsky', 'billing_rule' => 0]); 151 | 152 | $this->assertInstanceOf(BillingRule::class, $clientAny->billing_rule); 153 | $this->assertNull($clientAny->billing_rule->value()); 154 | 155 | $this->assertInstanceOf(BillingRule::class, $clientInvoiceOnly->billing_rule); 156 | $this->assertEquals(1, $clientInvoiceOnly->billing_rule->value()); 157 | 158 | $this->assertInstanceOf(BillingRule::class, $clientNoInvoice->billing_rule); 159 | $this->assertEquals(0, $clientNoInvoice->billing_rule->value()); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/EnumMutatorTest.php: -------------------------------------------------------------------------------- 1 | 'NMLM1HQ', 29 | 'status' => OrderStatus::SUBMITTED 30 | ]); 31 | 32 | $this->assertTrue($order->status->equals(OrderStatus::SUBMITTED())); 33 | 34 | $order->status = OrderStatus::PROCESSING(); 35 | $order->save(); 36 | 37 | $this->assertTrue($order->status->equals(OrderStatus::PROCESSING())); 38 | } 39 | 40 | /** 41 | * @test 42 | */ 43 | public function it_accepts_enums_primitive_value_as_well_when_setting_value() 44 | { 45 | $order = Order::create([ 46 | 'number' => 'TFOD578', 47 | 'status' => OrderStatus::SUBMITTED 48 | ]); 49 | 50 | $order->status = OrderStatus::PROCESSING; 51 | $order->save(); 52 | 53 | $this->assertTrue($order->status->equals(OrderStatus::PROCESSING())); 54 | } 55 | 56 | /** 57 | * @test 58 | */ 59 | public function it_accepts_enum_object_on_mass_assignment() 60 | { 61 | $order = Order::create([ 62 | 'number' => 'MIAAVC7', 63 | 'status' => OrderStatus::PROCESSING() 64 | ]); 65 | 66 | $this->assertTrue($order->status->equals(OrderStatus::PROCESSING())); 67 | 68 | $order->update(['status' => OrderStatus::SHIPPING()]); 69 | 70 | $this->assertTrue($order->status->equals(OrderStatus::SHIPPING())); 71 | } 72 | 73 | /** 74 | * @test 75 | */ 76 | public function it_doesnt_accept_scalars_that_arent_valid_enum_values() 77 | { 78 | $this->expectException(\UnexpectedValueException::class); 79 | $order = new Order(); 80 | $order->status = 'wtf'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/EnumToArrayTest.php: -------------------------------------------------------------------------------- 1 | 'abc123', 35 | 'status' => OrderStatus::SUBMITTED 36 | ]); 37 | 38 | $array = $order->attributesToArray(); 39 | 40 | $this->assertArrayHasKey('status', $array); 41 | $this->assertIsString($array['status']); 42 | } 43 | 44 | /** 45 | * @test 46 | */ 47 | public function still_returns_other_attributes() 48 | { 49 | $order = new Order([ 50 | 'number' => 'abc123', 51 | 'status' => OrderStatus::SUBMITTED 52 | ]); 53 | 54 | $array = $order->attributesToArray(); 55 | 56 | $this->assertArrayHasKey('number', $array); 57 | $this->assertEquals($array['number'], $order->number); 58 | } 59 | 60 | /** 61 | * @test 62 | */ 63 | public function to_array_still_works() 64 | { 65 | $order = new Order([ 66 | 'number' => 'abc123', 67 | 'status' => OrderStatus::SUBMITTED 68 | ]); 69 | 70 | $attributesArray = $order->attributesToArray(); 71 | $array = $order->toArray(); 72 | 73 | $this->assertEquals($array, $attributesArray); 74 | } 75 | 76 | /** 77 | * @test 78 | */ 79 | public function returns_enum_default_string_value_when_attribute_is_null() 80 | { 81 | // don't test if major version is lower than 3 82 | if ($this->getEnumVersionMajor() < 3) { 83 | $this->assertTrue(true); 84 | 85 | return; 86 | } 87 | 88 | $order = new Order([ 89 | 'number' => 'abc123', 90 | 'status' => null 91 | ]); 92 | 93 | $array = $order->attributesToArray(); 94 | 95 | $this->assertArrayHasKey('status', $array); 96 | $this->assertIsString($array['status']); 97 | $this->assertEquals($array['status'], OrderStatus::__DEFAULT); 98 | } 99 | 100 | /** @test */ 101 | public function it_does_not_set_the_attribute_key_if_the_attribute_is_absent_in_the_model() 102 | { 103 | $order = new Order([ 104 | 'number' => 'abc123' 105 | ]); 106 | 107 | $array = $order->attributesToArray(); 108 | $this->assertArrayNotHasKey('status', $array); 109 | } 110 | 111 | /** 112 | * @test 113 | */ 114 | public function returns_enum_v2_default_string_value_when_attribute_is_null() 115 | { 116 | // don't test if major version is 3 or higher 117 | if ($this->getEnumVersionMajor() >= 3) { 118 | $this->assertTrue(true); 119 | 120 | return; 121 | } 122 | 123 | $order = new OrderV2([ 124 | 'number' => 'abc123', 125 | 'status' => null 126 | ]); 127 | 128 | $array = $order->attributesToArray(); 129 | 130 | $this->assertArrayHasKey('status', $array); 131 | $this->assertIsString($array['status']); 132 | $this->assertEquals($array['status'], OrderStatusV2::__default); 133 | } 134 | 135 | /** @test */ 136 | public function returns_no_enum_if_hidden() 137 | { 138 | $normal = new Visibility([ 139 | 'number' => 'na321', 140 | 'talk1' => VisibilityTalk::BLABLA, 141 | 'talk2' => VisibilityTalk::YADDA, 142 | ]); 143 | 144 | $array = $normal->attributesToArray(); 145 | 146 | $this->assertArrayHasKey('number', $array); 147 | $this->assertArrayNotHasKey('talk1', $array); 148 | $this->assertArrayHasKey('talk2', $array); 149 | $this->assertIsString($array['talk2']); 150 | $this->assertEquals($array['talk2'], VisibilityTalk::YADDA); 151 | } 152 | 153 | /** @test */ 154 | public function returns_no_enum_if_hidden_dynamic() 155 | { 156 | $dynamic_hidden = new Visibility([ 157 | 'number' => 'na654', 158 | 'talk1' => VisibilityTalk::BLABLA, 159 | 'talk2' => VisibilityTalk::YADDA, 160 | ]); 161 | 162 | $dynamic_hidden->makeHidden('talk2'); 163 | 164 | $array = $dynamic_hidden->attributesToArray(); 165 | 166 | $this->assertArrayHasKey('number', $array); 167 | $this->assertArrayNotHasKey('talk1', $array); 168 | $this->assertArrayNotHasKey('talk2', $array); 169 | } 170 | 171 | /** @test */ 172 | public function returns_enum_if_hidden_made_visible() 173 | { 174 | $made_visible = new Visibility([ 175 | 'number' => 'na987', 176 | 'talk1' => VisibilityTalk::BLABLA, 177 | 'talk2' => VisibilityTalk::YADDA, 178 | ]); 179 | 180 | $made_visible->makeVisible('talk1'); 181 | 182 | $array = $made_visible->attributesToArray(); 183 | 184 | $this->assertArrayHasKey('number', $array); 185 | 186 | $this->assertArrayHasKey('talk1', $array); 187 | $this->assertIsString($array['talk1']); 188 | $this->assertEquals($array['talk1'], VisibilityTalk::BLABLA); 189 | 190 | $this->assertArrayHasKey('talk2', $array); 191 | $this->assertIsString($array['talk2']); 192 | $this->assertEquals($array['talk2'], VisibilityTalk::YADDA); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/Models/Address.php: -------------------------------------------------------------------------------- 1 | 'Konekt\\Enum\\Eloquent\\Tests\\Resolvers\\AddressTypeResolver@enumClass', 28 | 'status' => 'AddressStatusResolver@enumClass' 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /tests/Models/AddressStatus.php: -------------------------------------------------------------------------------- 1 | BillingRule::class, 34 | ]; 35 | } 36 | -------------------------------------------------------------------------------- /tests/Models/Eloquent.php: -------------------------------------------------------------------------------- 1 | 'EloquentTypeProxy@enumClass' 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /tests/Models/EloquentType.php: -------------------------------------------------------------------------------- 1 | 'boolean' 28 | ]; 29 | 30 | protected $enums = [ 31 | 'status' => OrderStatus::class 32 | ]; 33 | 34 | /** 35 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 36 | */ 37 | public function client() 38 | { 39 | return $this->belongsTo(Client::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Models/OrderStatus.php: -------------------------------------------------------------------------------- 1 | 'boolean' 30 | ]; 31 | 32 | protected $enums = [ 33 | 'status' => OrderStatusV2::class 34 | ]; 35 | 36 | /** 37 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 38 | */ 39 | public function client() 40 | { 41 | return $this->belongsTo(Client::class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Models/OrderVX.php: -------------------------------------------------------------------------------- 1 | 'boolean' 28 | ]; 29 | 30 | protected $enums = [ 31 | 'status' => OrderStatusVX::class 32 | ]; 33 | 34 | /** 35 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 36 | */ 37 | public function client() 38 | { 39 | return $this->belongsTo(Client::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Models/Visibility.php: -------------------------------------------------------------------------------- 1 | 'boolean' 28 | ]; 29 | 30 | protected $enums = [ 31 | 'talk1' => VisibilityTalk::class, 32 | 'talk2' => VisibilityTalk::class, 33 | ]; 34 | 35 | protected $hidden = [ 36 | 'talk1', 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /tests/Models/VisibilityTalk.php: -------------------------------------------------------------------------------- 1 | setUpDatabase(); 31 | } 32 | 33 | protected function setUpDatabase() 34 | { 35 | $this->capsule = new Capsule(); 36 | $this->capsule->addConnection([ 37 | 'driver' => 'sqlite', 38 | 'database' => ':memory:', 39 | ]); 40 | 41 | $this->capsule->setEventDispatcher(new Dispatcher()); 42 | $this->capsule->setAsGlobal(); 43 | $this->capsule->bootEloquent(); 44 | 45 | $this->capsule->schema()->dropIfExists('orders'); 46 | $this->capsule->schema()->dropIfExists('clients'); 47 | $this->capsule->schema()->dropIfExists('addresses'); 48 | 49 | $this->capsule->schema()->create('orders', function (Blueprint $table) { 50 | $table->increments('id'); 51 | $table->string('number'); 52 | $table->integer('client_id')->unsigned()->nullable(); 53 | $table->enum('status', OrderStatus::values()); 54 | $table->boolean('is_active')->default(true); 55 | $table->timestamps(); 56 | }); 57 | 58 | $this->capsule->schema()->create('clients', function (Blueprint $table) { 59 | $table->increments('id'); 60 | $table->string('name'); 61 | $table->integer('billing_rule')->nullable(); 62 | $table->timestamps(); 63 | }); 64 | 65 | $this->capsule->schema()->create('addresses', function (Blueprint $table) { 66 | $table->increments('id'); 67 | $table->string('type'); 68 | $table->string('status')->nullable(); 69 | $table->string('address')->nullable(); 70 | $table->timestamps(); 71 | }); 72 | 73 | $this->capsule->schema()->create('eloquents', function (Blueprint $table) { 74 | $table->increments('id'); 75 | $table->string('type')->nullable(); 76 | $table->timestamps(); 77 | }); 78 | } 79 | } 80 | --------------------------------------------------------------------------------