├── .codacy.yml ├── .editorconfig ├── .github └── workflows │ ├── phpstan.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── composer.json ├── config └── status.php ├── database └── migrations │ └── create_statuses_table.php.stub ├── phpstan.neon ├── phpunit.coverage.dist.xml ├── phpunit.dist.xml ├── src ├── Contracts │ └── Statusable.php ├── EventServiceProvider.php ├── Events │ └── StatusCreating.php ├── Listeners │ └── AttachDefaultStatus.php ├── Nova │ └── Filters │ │ └── StatusFilter.php ├── Scopes │ └── DefaultStatusScope.php ├── ServiceProvider.php ├── Status.php ├── StatusBuilder.php └── Traits │ └── HasStatuses.php └── tests ├── Fixtures ├── AnotherStatuses.php ├── Post.php └── PostStatuses.php ├── StatusTest.php ├── TestCase.php └── database └── 0000_00_00_000000_create_posts_test_table.php /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | phpcs: 4 | php_version: 7.4- 5 | 6 | exclude_paths: 7 | - "**/tests/**" 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = true 13 | 14 | [*.{yml,xml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/workflows/phpstan.yml: -------------------------------------------------------------------------------- 1 | name: phpstan 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | analyze: 7 | runs-on: ubuntu-latest 8 | 9 | name: PHPStan 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup PHP 15 | uses: shivammathur/setup-php@v2 16 | with: 17 | php-version: 8.1 18 | 19 | - name: Cache dependencies 20 | uses: actions/cache@v2 21 | with: 22 | path: ${{ steps.composer-cache.outputs.dir }} 23 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 24 | restore-keys: ${{ runner.os }}-composer-prefer-stable- 25 | 26 | - name: Install dependencies 27 | run: | 28 | composer update --prefer-stable --prefer-dist --no-interaction --no-suggest 29 | 30 | - name: Run analysis 31 | run: ./vendor/bin/phpstan analyse 32 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | os: [ ubuntu-latest ] 13 | php: [ 7.4, 8.0, 8.1 ] 14 | laravel: [ 6.*, 8.*, 9.* ] 15 | dependency-version: [ prefer-stable ] 16 | exclude: 17 | # PHP 8.1 is not compatible with Laravel 6! 18 | - laravel: 6.* 19 | php: 8.1 20 | # PHP 7 is not compatible with Laravel 9 and future releases! 21 | - laravel: 9.* 22 | php: 7.4 23 | include: 24 | - laravel: 6.* 25 | testbench: 4.* 26 | 27 | - laravel: 8.* 28 | testbench: 6.* 29 | 30 | - laravel: 9.* 31 | testbench: 7.* 32 | 33 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 34 | 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@v1 38 | 39 | - name: Setup PHP 40 | uses: shivammathur/setup-php@v2 41 | with: 42 | php-version: ${{ matrix.php }} 43 | extensions: dom, curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, intl, exif 44 | coverage: pcov 45 | 46 | - name: Get composer cache directory 47 | id: composer-cache 48 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 49 | 50 | - name: Cache dependencies 51 | uses: actions/cache@v2 52 | with: 53 | path: ${{ steps.composer-cache.outputs.dir }} 54 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 55 | restore-keys: ${{ runner.os }}-composer-${{ matrix.dependency-version }}- 56 | 57 | - name: Install dependencies 58 | env: 59 | NOVA_USERNAME: ${{ secrets.NOVA_USERNAME }} 60 | NOVA_PASSWORD: ${{ secrets.NOVA_PASSWORD }} 61 | run: | 62 | composer config http-basic.nova.laravel.com $NOVA_USERNAME $NOVA_PASSWORD 63 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 64 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 65 | 66 | - name: Execute tests 67 | run: | 68 | cp phpunit.coverage.dist.xml phpunit.xml || true 69 | vendor/bin/phpunit 70 | 71 | - name: Deploy coverage to codacy 72 | uses: codacy/codacy-coverage-reporter-action@v1 73 | with: 74 | # project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 75 | api-token: ${{ secrets.CODACY_API_TOKEN }} 76 | coverage-reports: clover.xml 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .phpunit.result.cache 4 | /xml-coverage/ 5 | clover.xml 6 | phpunit.xml 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.5.2] - 2022-07-15 11 | 12 | ### Fixed 13 | 14 | - Missing autoload-dev, tests were autoloaded with the released version (ouch!) 15 | 16 | ## [2.5.1] - 2022-06-20 17 | 18 | ### Fixed 19 | 20 | - Fix Laravel Nova status filter 21 | 22 | ## [2.5.0] - 2022-06-06 23 | 24 | ### Added 25 | 26 | - `statuses` query scope that accepts multiple statuses names (labels) 27 | 28 | ## [2.4.1] - 2022-03-03 29 | 30 | ### Fixed 31 | 32 | - `creating` event was preventing propagation 33 | 34 | ## [2.4.0] - 2022-02-11 35 | 36 | ### Added 37 | 38 | - Support for Laravel 9 39 | 40 | ### Removed 41 | 42 | - Support for Laravel 7 43 | 44 | ## [2.3.3] - 2021-11-24 45 | 46 | ### Fixed 47 | 48 | - AttachDefaultStatus event population when creating 49 | 50 | ## [2.3.2] - 2021-08-06 51 | 52 | ### Fixed 53 | 54 | - Fix bool return when `setStatus` and `status` methods 55 | 56 | ## [2.3.1] - 2021-07-28 57 | 58 | ### Fixed 59 | 60 | - Camel cased on status checks & toEnums internal functions 61 | 62 | ## [2.3.0] - 2021-07-13 63 | 64 | ### Added 65 | 66 | - More test coverage 67 | 68 | ### Changed 69 | 70 | - More methods now accepting enum classes as inputs: `setStatus` & `setStatusWhen` 71 | 72 | ### Removed 73 | 74 | - Internal method `checkStatus` 75 | 76 | ## [2.2.0] - 2021-07-12 77 | 78 | ### Changed 79 | 80 | - Fixes around conditional assignation `setStatus(['previous' => 'new'])` 81 | 82 | ### Added 83 | 84 | - `Status::toEnum` utility method for transform string value to enum object 85 | - `Model::setStatusWhen` method which works similarly the same as `setStatus` 86 | - More tests around all the exposed and internal methods 87 | 88 | ## [2.1.1] - 2021-07-09 89 | 90 | ### Changed 91 | 92 | - `Model::status()` query scope now accepts enum instances 93 | 94 | ## [2.1.0] - 2021-07-09 95 | 96 | ### Changed 97 | 98 | - `Model::hasStatus()` now accepts enum instances 99 | - Required package `spatie/enum` upgraded to v3 100 | - A lot of simplification all over the place 101 | - Deprecated static method `Status::getDefault()` use instead `Status::defaultFrom($model)` query scope 102 | 103 | ### Added 104 | 105 | - `Status::defaultFrom($model)` local query scope 106 | - `Statusable::statusesClass` static method to get Statuses enum (can be also replaced to your own path) 107 | - Some package tests covering most of its code 108 | 109 | ### Removed 110 | 111 | - `Statusable::statuses` static propery has been removed in favor of `Statusable::statusesClass` to be user configurable 112 | 113 | ## [2.0.3] - 2021-02-23 114 | 115 | ### Fixed 116 | 117 | - Remove lazy eager load of relation on trait (fixes some issues displaying the status relationship on APIs when not requested) 118 | 119 | ## [2.0.2] - 2021-01-20 120 | 121 | ### Fixed 122 | 123 | - Wrong StatusFilter query in Nova 124 | 125 | ## [2.0.1] - 2021-01-20 126 | 127 | ### Added 128 | 129 | - Support for PHP 8 130 | - Default model status query scope class 131 | - Laravel Nova status filter class 132 | 133 | ## [2.0.0] - 2020-11-30 134 | 135 | ### Added 136 | 137 | - Support for Laravel 8 138 | 139 | ### Removed 140 | 141 | - Support for Laravel 5 142 | 143 | ## [1.3.4] - 2020-07-06 144 | 145 | ### Fixed 146 | 147 | - HasStatus method returning wrong thing ([also documented](https://github.com/skore/laravel-status#hasStatus)) 148 | 149 | ## [1.3.3] - 2020-07-02 150 | 151 | ### Fixed 152 | 153 | - HasStatus method was returning first string char 154 | 155 | ## [1.3.2] - 2020-04-06 156 | 157 | ### Fixed 158 | 159 | - Rename folder / fix namespace (PSR-4) 160 | 161 | ## [1.3.1] - 2020-03-13 162 | 163 | ### Added 164 | 165 | - Missed config option to the file 166 | 167 | ### Changed 168 | 169 | - Default to true enable_events config option 170 | 171 | ## [1.3.0] - 2020-03-13 172 | 173 | ### Added 174 | 175 | - Config option for enable or disable all the package events 176 | 177 | ### Fixed 178 | 179 | - Change trait event from Model's `dispatchesEvents` to passing callbacks methods 180 | - Saving from a replaced Model's built-in method to another event (`saving`) 181 | 182 | ## [1.2.4] - 2020-03-10 183 | 184 | ### Fixed 185 | 186 | - More fixes around status setter 187 | 188 | ## [1.2.3] - 2020-03-09 189 | 190 | ### Fixed 191 | 192 | - Fixed some issues checking statuses on `setStatus(['previous' => 'new'])` 193 | 194 | ### Changed 195 | 196 | - Minor changes to code style 197 | 198 | ## [1.2.2] - 2020-03-06 199 | 200 | ### Fixed 201 | 202 | - Improved case sensitivity in get/set statuses 203 | - Events names with spaces 204 | 205 | ## [1.2.1] - 2020-03-06 206 | 207 | ### Fixed 208 | 209 | - `HasStatuses::save()` return bool 210 | 211 | ## [1.2.0] - 2020-03-05 212 | 213 | ### Added 214 | 215 | - Compatibility with Laravel 7 216 | - Custom model events on status `saving` and `saved` (e.g. `savingActive` & `savedActive` when save a *non active* model to active) 217 | - EventsServiceProvider (**no need to manually add events to your app's events**) 218 | 219 | ### Changed 220 | 221 | - **Possible breakchange!** Renamed package ServiceProvider (from _StatusServiceProvider_ to _ServiceProvider_) 222 | 223 | ## [1.1.5] - 2020-03-05 224 | 225 | ### Changed 226 | 227 | - Minor changes and optimisations 228 | - Change default config path for models (following Laravel's default one: `App\Model`) 229 | 230 | ## [1.1.4] - 2020-01-12 231 | 232 | ### Fixed 233 | 234 | - Get model class in `Status::getFromEnum()` method (now using `Model::getMorphClass()`) 235 | 236 | ## [1.1.3] - 2020-01-11 237 | 238 | ### Fixed 239 | 240 | - More trait fixes around `getMorphClass()` and the new addons 241 | 242 | ## [1.1.2] - 2020-01-11 243 | 244 | ### Added 245 | 246 | - Custom getter in `HasStatuses` trait for Status custom model 247 | 248 | ### Fixed 249 | 250 | - Get properly the model's morph class by using `getMorphClass()` 251 | 252 | ## [1.1.1] - 2019-12-10 253 | 254 | ### Fixed 255 | 256 | - checkStatus array_walk to array_map 257 | - case sensitive in setStatusAttribute 258 | 259 | ## [1.1.0] - 2019-12-10 260 | 261 | ### Added 262 | 263 | - Use custom Status model (customisable in the config file) 264 | 265 | ### Fixed 266 | 267 | - hasStatus didn't load relation properly 268 | 269 | ### Changed 270 | 271 | - Changes to the config file 272 | 273 | ## [1.0.2] - 2019-12-07 274 | 275 | ### Fixed 276 | 277 | - Variable types and names in docblocks 278 | - Case sensitive in status attribute mutator 279 | 280 | ## [1.0.1] - 2019-12-07 281 | 282 | ### Fixed 283 | 284 | - Missed namespace on Statusable contract 285 | 286 | ## [1.0.0] - 2019-12-07 287 | 288 | ### Added 289 | 290 | - Package published on Packagist (composer) 291 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Skore 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 | # Laravel status 2 | 3 | > ⚠️ **This package is gonna be deprecated in favour of this one: https://github.com/open-southeners/laravel-model-status** 4 | 5 | Laravel code-typed statuses for Eloquent models. 6 | 7 | ## Status 8 | 9 | [![packagist version](https://img.shields.io/packagist/v/skore-labs/laravel-status)](https://packagist.org/packages/skore-labs/laravel-status) [![tests](https://github.com/skore/laravel-status/actions/workflows/tests.yml/badge.svg)](https://github.com/skore/laravel-status/actions/workflows/tests.yml) [![StyleCI](https://github.styleci.io/repos/226506454/shield?style=flat&branch=master)](https://github.styleci.io/repos/226506454) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/33ee151f19bd45f99ddcfcaeca621327)](https://www.codacy.com/gh/skore/laravel-status/dashboard?utm_source=github.com&utm_medium=referral&utm_content=skore/laravel-status&utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/33ee151f19bd45f99ddcfcaeca621327)](https://www.codacy.com/gh/skore/laravel-status/dashboard?utm_source=github.com&utm_medium=referral&utm_content=skore/laravel-status&utm_campaign=Badge_Coverage) [![Scc Count Badge](https://sloc.xyz/github/skore/laravel-status?category=code)](https://github.com/skore/laravel-status) [![Scc Count Badge](https://sloc.xyz/github/skore/laravel-status?category=comments)](https://github.com/skore/laravel-status) 10 | 11 | ## Getting started 12 | 13 | You can install the package via composer: 14 | 15 | ``` 16 | composer require skore-labs/laravel-status 17 | ``` 18 | 19 | Then you will need to publish the package config and migrations, so then you can modify and/or migrate the new statuses table: 20 | 21 | ``` 22 | php artisan vendor:publish --provider="SkoreLabs\LaravelStatus\ServiceProvider" 23 | ``` 24 | 25 | ### Setup models 26 | 27 | Add statuses to your model by adding `SkoreLabs\LaravelStatus\Traits\HasStatuses` and the interface `SkoreLabs\LaravelStatus\Contracts\Statusable` so that it can pass some predefined events (see above), here's an example: 28 | 29 | ```php 30 | hasStatus('published'); 82 | 83 | // Post has status Published or Was Published 84 | $post->hasStatus(['published', 'was published']); 85 | ``` 86 | 87 | ### setStatus 88 | 89 | Set status or mutate status **only if the previous status match the key.** 90 | 91 | ```php 92 | // Set post status to Was Published 93 | $post->setStatus('was published'); 94 | 95 | // Change if post has status Published to Was Published. 96 | $post->setStatus(['published' => 'was published']); 97 | ``` 98 | 99 | You can also use the attribute to set a status: 100 | 101 | ```php 102 | $post->status = 'was published'; 103 | 104 | // Better use status method for this 105 | if ($post->hasStatus('published')) { 106 | $post->status = 'was published'; 107 | } 108 | 109 | // When save it check and attach the status 110 | $post->save(); 111 | ``` 112 | 113 | ### setStatusWhen 114 | 115 | You can also do the same with `setStatusWhen` method like the example above with `setStatus`. 116 | 117 | ```php 118 | // Change if post has status Published to Was Published. 119 | $post->setStatusWhen('published', 'was published'); 120 | ``` 121 | 122 | ### status 123 | 124 | If a parameter is provided, it acts as an alias of [hasStatus](#hasStatus). 125 | 126 | If an associative array is provided, it acts as an alias of [setStatus](#setStatus). 127 | 128 | Otherwise, it will just retrieve the relationship as `$post->status` or `$post->status()->first()` 129 | 130 | Also you can filter by scope: 131 | 132 | ```php 133 | Post::status('published'); 134 | Post::where('user_id', Auth::id())->status('published'); 135 | ``` 136 | 137 | ### statuses 138 | 139 | Get all the possible model statuses. 140 | 141 | ```php 142 | Post::statuses(); 143 | 144 | // You can use Status model as well 145 | Status::getFrom(Post::class); 146 | // Also specify value to return like '->value('id')' 147 | Status::getFrom(Post::class, 'id'); 148 | // Or return the object with columns like '->first(['id', 'name'])' 149 | Status::getFrom(Post::class, ['id', 'name']); 150 | ``` 151 | 152 | ### getDefaultStatus 153 | 154 | Get the model's default status. 155 | 156 | ```php 157 | // Default status for post is Published, so it returns Published 158 | Post::getDefaultStatus(); 159 | 160 | // You can use Status model query scope as well 161 | Status::query()->defaultFrom(Post::class)->first(); 162 | ``` 163 | 164 | ## Support 165 | 166 | This and all of our Laravel packages follows as much as possibly can the LTS support of Laravel. 167 | 168 | Read more: https://laravel.com/docs/master/releases#support-policy 169 | 170 | ## Credits 171 | 172 | - Ruben Robles ([@d8vjork](https://github.com/d8vjork)) 173 | - Skore ([https://www.getskore.com/](https://www.getskore.com/)) 174 | - Spatie for the Enum package ([https://spatie.be/](https://spatie.be/)) 175 | - [And all the contributors](https://github.com/skore-labs/laravel-status/graphs/contributors) 176 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We're also following the [Laravel's default support policy](https://laravel.com/docs/master/releases#support-policy) on our semantic versioning. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 2.x | :white_check_mark: | 10 | | < 2.0 | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Send us an email at developers@getskore.com 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skore-labs/laravel-status", 3 | "description": "Laravel code-typed statuses for models", 4 | "license": "MIT", 5 | "keywords": [ 6 | "skore-labs", 7 | "laravel", 8 | "statuses", 9 | "eloquent", 10 | "typed" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Ruben Robles", 15 | "email": "d8vjork@outlook.com" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.2|^8.0|^8.1", 20 | "spatie/enum": "^3.9", 21 | "illuminate/database": "^6.0|^8.0|^9.0", 22 | "illuminate/support": "^6.0|^8.0|^9.0" 23 | }, 24 | "require-dev": { 25 | "ext-json": "*", 26 | "laravel/nova": "^2.0|^3.0", 27 | "nunomaduro/larastan": "^1.0|^2.0", 28 | "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", 29 | "phpstan/phpstan": "^1.4", 30 | "phpunit/phpunit": "^7.0|^9.0" 31 | }, 32 | "suggest": { 33 | "laravel/nova": "Required if using status nova filter" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "SkoreLabs\\LaravelStatus\\": "src" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "SkoreLabs\\LaravelStatus\\Tests\\": "tests" 43 | } 44 | }, 45 | "config": { 46 | "sort-packages": true 47 | }, 48 | "extra": { 49 | "laravel": { 50 | "providers": [ 51 | "SkoreLabs\\LaravelStatus\\ServiceProvider" 52 | ] 53 | } 54 | }, 55 | "repositories": [ 56 | { 57 | "type": "composer", 58 | "url": "https://nova.laravel.com" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /config/status.php: -------------------------------------------------------------------------------- 1 | SkoreLabs\LaravelStatus\Status::class, 6 | 7 | 'enums_path' => 'App\\Enums\\', 8 | 9 | 'models_path' => 'App\\', 10 | 11 | 'enable_events' => true, 12 | 13 | ]; 14 | -------------------------------------------------------------------------------- /database/migrations/create_statuses_table.php.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 13 | $table->string('name'); 14 | $table->string('model_type'); 15 | $table->boolean('is_default')->default(false); 16 | 17 | $table->unique(['name', 'model_type']); 18 | }); 19 | } 20 | 21 | public function down() 22 | { 23 | Schema::dropIfExists('statuses'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src 8 | 9 | level: 6 10 | 11 | # ignoreErrors: 12 | # - '#PHPDoc tag @var#' 13 | 14 | checkMissingIterableValueType: false 15 | -------------------------------------------------------------------------------- /phpunit.coverage.dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./src 18 | 19 | 20 | src/EventServiceProvider.php 21 | src/Contracts/Statusable.php 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | tests 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /phpunit.dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | src/ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | tests 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Contracts/Statusable.php: -------------------------------------------------------------------------------- 1 | |string|bool 22 | */ 23 | public function status($value = false); 24 | 25 | /** 26 | * Get statuses available for this model as attribute. 27 | * 28 | * @return array 29 | */ 30 | public function getStatusesAttribute(); 31 | 32 | /** 33 | * Get statuses available for this model. 34 | * 35 | * @return array 36 | */ 37 | public static function getStatuses(); 38 | 39 | /** 40 | * Set status by label(s) to key and perform a save. 41 | * 42 | * @param array|string|\Spatie\Enum\Enum $name 43 | * 44 | * @return bool 45 | */ 46 | public function setStatus($name); 47 | 48 | /** 49 | * Set status when current status is. 50 | * 51 | * @param mixed $current 52 | * @param mixed $new 53 | * 54 | * @return bool 55 | */ 56 | public function setStatusWhen($current, $new); 57 | 58 | /** 59 | * Set status relation as attribute. 60 | * 61 | * @param string|\Spatie\Enum\Enum $value 62 | * 63 | * @return void 64 | */ 65 | public function setStatusAttribute($value = null); 66 | 67 | /** 68 | * Check current status is equals to. 69 | * 70 | * @param string|array|\Spatie\Enum\Enum $value 71 | * 72 | * @return bool 73 | */ 74 | public function hasStatus($value); 75 | 76 | /** 77 | * Get model status or default instead. 78 | * 79 | * @param string $column 80 | * 81 | * @return string|null 82 | */ 83 | public function getStatus($column = 'name'); 84 | 85 | /** 86 | * Get default status for this model. 87 | * 88 | * @param string|array $column 89 | * 90 | * @return \Illuminate\Database\Eloquent\Model|object|\Illuminate\Database\Eloquent\Builder|null|mixed 91 | */ 92 | public static function getDefaultStatus($column = 'name'); 93 | 94 | /** 95 | * List all resources of a specified status. 96 | * 97 | * @param \Illuminate\Database\Eloquent\Builder $query 98 | * @param string|\Spatie\Enum\Enum $name 99 | * 100 | * @return \Illuminate\Database\Eloquent\Builder 101 | */ 102 | public function scopeStatus(Builder $query, $name); 103 | } 104 | -------------------------------------------------------------------------------- /src/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 18 | AttachDefaultStatus::class, 19 | ], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/Events/StatusCreating.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public $model; 18 | 19 | /** 20 | * Create a new event instance. 21 | * 22 | * @param \SkoreLabs\LaravelStatus\Contracts\Statusable<\Illuminate\Database\Eloquent\Model> $statusable 23 | * 24 | * @return void 25 | */ 26 | public function __construct(Statusable $statusable) 27 | { 28 | $this->model = $statusable; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Listeners/AttachDefaultStatus.php: -------------------------------------------------------------------------------- 1 | model->status_id || !$event->model->status) { 19 | $event->model->status()->associate( 20 | $event->model->getDefaultStatus('id') 21 | ); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Nova/Filters/StatusFilter.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | protected $statusesQuery; 18 | 19 | /** 20 | * @var array|bool 21 | */ 22 | protected $defaultOption = false; 23 | 24 | /** 25 | * Create a new filter instance. 26 | * 27 | * @param string $type 28 | * 29 | * @return void 30 | */ 31 | public function __construct($type) 32 | { 33 | $this->statusesQuery = Status::where([ 34 | 'model_type' => $type, 35 | ]); 36 | } 37 | 38 | /** 39 | * Apply the filter to the given query. 40 | * 41 | * @param \Illuminate\Http\Request $request 42 | * @param \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> $query 43 | * @param mixed $value 44 | * 45 | * @return \Illuminate\Database\Eloquent\Builder<\Illuminate\Database\Eloquent\Model> 46 | */ 47 | public function apply(Request $request, $query, $value) 48 | { 49 | if (!array_filter($value)) { 50 | return $query; 51 | } 52 | 53 | $i = 0; 54 | 55 | foreach ($value as $status => $enabled) { 56 | if ($enabled) { 57 | $query->where($query->getModel()->getTable().'.status_id', '=', $status, $i === 0 ? 'and' : 'or'); 58 | $i++; 59 | } 60 | } 61 | 62 | return $query->withoutGlobalScope(DefaultStatusScope::class); 63 | } 64 | 65 | /** 66 | * Get the filter's available options. 67 | * 68 | * @param \Illuminate\Http\Request $request 69 | * 70 | * @return array 71 | */ 72 | public function options(Request $request) 73 | { 74 | return (clone $this->statusesQuery) 75 | ->pluck('id', 'name') 76 | ->all(); 77 | } 78 | 79 | /** 80 | * Set the default options for the filter. 81 | * 82 | * @return array|bool 83 | */ 84 | public function default() 85 | { 86 | if (!$this->defaultOption) { 87 | return parent::default(); 88 | } 89 | 90 | return $this->defaultOption; 91 | } 92 | 93 | /** 94 | * Set option as default initial filter. 95 | * 96 | * @param string $value 97 | * 98 | * @return $this 99 | */ 100 | public function setDefault($value) 101 | { 102 | $query = (clone $this->statusesQuery)->where('name', $value); 103 | 104 | if ($query->exists()) { 105 | $this->defaultOption = [$query->value('id') => true]; 106 | } 107 | 108 | return $this; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Scopes/DefaultStatusScope.php: -------------------------------------------------------------------------------- 1 | $builder 18 | * @param \Illuminate\Database\Eloquent\Model $model 19 | * 20 | * @return void 21 | */ 22 | public function apply(Builder $builder, Model $model) 23 | { 24 | /** @var \SkoreLabs\LaravelStatus\Contracts\Statusable $model */ 25 | $builder->where('status_id', $model->getDefaultStatus('id')); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 17 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations/'); 18 | } 19 | 20 | if (!class_exists('CreateStatusesTable')) { 21 | $timestamp = date('Y_m_d_His', time()); 22 | 23 | $this->publishes([ 24 | __DIR__.'/../database/migrations/create_statuses_table.php.stub' => database_path("migrations/{$timestamp}_create_statuses_table.php"), 25 | ], 'migrations'); 26 | } 27 | 28 | $this->publishes([ 29 | __DIR__.'/../config/status.php' => config_path('status.php'), 30 | ], 'config'); 31 | } 32 | 33 | /** 34 | * Register any application services. 35 | * 36 | * @return void 37 | */ 38 | public function register() 39 | { 40 | if (config('status.enable_events', true)) { 41 | $this->app->register(EventServiceProvider::class); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Status.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected $fillable = [ 24 | 'name', 'model_type', 'is_default', 25 | ]; 26 | 27 | /** 28 | * The attributes that should be visible in arrays. 29 | * 30 | * @var array 31 | */ 32 | protected $visible = ['name']; 33 | 34 | /** 35 | * Get default status from model. 36 | * 37 | * @param class-string<\Illuminate\Database\Eloquent\Model> $modelClass 38 | * @param string|array $column 39 | * 40 | * @return mixed 41 | * 42 | * @deprecated Removing this method on next major release of "skore-labs/laravel-status" 43 | */ 44 | public static function getDefault($modelClass, $column = 'id') 45 | { 46 | /** @var \Illuminate\Database\Eloquent\Builder<\SkoreLabs\LaravelStatus\Status> $baseQuery */ 47 | $baseQuery = self::where('model_type', $modelClass); 48 | 49 | $query = $baseQuery->where('is_default', true); 50 | 51 | if (!$query->exists()) { 52 | $query = $baseQuery; 53 | } 54 | 55 | if (is_array($column)) { 56 | return $query->first($column); 57 | } 58 | 59 | return $query->value($column); 60 | } 61 | 62 | /** 63 | * Get column from status enum class. 64 | * 65 | * @param \Spatie\Enum\Enum $enum 66 | * @param \Illuminate\Database\Eloquent\Model $from 67 | * @param string|array $column 68 | * 69 | * @return \Illuminate\Database\Eloquent\Model|object|\Illuminate\Database\Eloquent\Builder|null|mixed 70 | */ 71 | public static function getFromEnum(Enum $enum, Model $from, $column = 'id') 72 | { 73 | $fromModelMorphClass = $from->getMorphClass(); 74 | 75 | $query = self::query() 76 | ->where('model_type', $fromModelMorphClass) 77 | ->where('name', 'like', "%{$enum->label}%"); 78 | 79 | if ($query->count('id') === 0) { 80 | $query->orWhere(function (Builder $query) use ($fromModelMorphClass) { 81 | $query->defaultFrom($fromModelMorphClass); 82 | }); 83 | } 84 | 85 | return is_array($column) 86 | ? $query->first($column) 87 | : $query->value($column); 88 | } 89 | 90 | /** 91 | * Wrap status value into status enum class. 92 | * 93 | * @param mixed|\Spatie\Enum\Enum $class 94 | * @param mixed $value 95 | * 96 | * @return \Spatie\Enum\Enum|false 97 | */ 98 | public static function toEnum($class, $value) 99 | { 100 | if (!$value || !method_exists($class, 'tryFrom')) { 101 | return false; 102 | } 103 | 104 | if (!($value instanceof Enum)) { 105 | return $class::tryFrom($value); 106 | } 107 | 108 | return $value; 109 | } 110 | 111 | /** 112 | * Create a new Eloquent query builder for the model. 113 | * 114 | * @param \Illuminate\Database\Query\Builder $query 115 | * 116 | * @return \SkoreLabs\LaravelStatus\StatusBuilder 117 | */ 118 | public function newEloquentBuilder($query) 119 | { 120 | return new StatusBuilder($query); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/StatusBuilder.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class StatusBuilder extends Builder 12 | { 13 | /** 14 | * Get default status from model. 15 | * 16 | * @param string|\Illuminate\Database\Eloquent\Model $modelType 17 | * 18 | * @return $this 19 | */ 20 | public function defaultFrom($modelType) 21 | { 22 | $this->where(function (self $query) use ($modelType) { 23 | $query->where( 24 | 'model_type', 25 | $modelType instanceof Model 26 | ? $modelType->getMorphClass() 27 | : $modelType 28 | ); 29 | 30 | $query->where('is_default', true); 31 | }); 32 | 33 | return $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Traits/HasStatuses.php: -------------------------------------------------------------------------------- 1 | fillable = array_merge($this->fillable, ['status']); 40 | $this->guarded = array_merge($this->guarded, ['status_id']); 41 | 42 | if (config('status.enable_events', true)) { 43 | $this->addObservableEvents($this->getStatusObservables()); 44 | 45 | static::saving(function () { 46 | if ($this->savingStatus) { 47 | $this->savingStatus = false; 48 | $this->fireModelEvent('saved'.$this->formatStatusName($this->getStatus()), false); 49 | } 50 | }); 51 | } 52 | } 53 | 54 | /** 55 | * Get the statuses enum used for some utilities. 56 | * 57 | * @return string|\Spatie\Enum\Enum 58 | */ 59 | public static function statusesClass() 60 | { 61 | return config('status.enums_path').class_basename(self::class).'Status'; 62 | } 63 | 64 | /** 65 | * Get statuses custom observables events. 66 | * 67 | * @return array 68 | */ 69 | protected function getStatusObservables() 70 | { 71 | $statusEventsArr = []; 72 | $statuses = static::getStatuses(); 73 | 74 | foreach ($statuses as $status) { 75 | $status = $this->formatStatusName($status); 76 | 77 | $statusEventsArr[] = "saving${status}"; 78 | $statusEventsArr[] = "saved${status}"; 79 | } 80 | 81 | return $statusEventsArr; 82 | } 83 | 84 | /** 85 | * Get or set current status for this model. 86 | * 87 | * @param bool $value 88 | * 89 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo|string|bool 90 | */ 91 | public function status($value = false) 92 | { 93 | if ($value) { 94 | return is_array($value) && Arr::isAssoc($value) 95 | ? $this->setStatus($value) 96 | : $this->hasStatus($value); 97 | } 98 | 99 | return $this->belongsTo( 100 | config('status.use_model', Status::class) 101 | ); 102 | } 103 | 104 | /** 105 | * Get statuses available for this model as attribute. 106 | * 107 | * @return array 108 | */ 109 | public function getStatusesAttribute() 110 | { 111 | return static::getStatuses(); 112 | } 113 | 114 | /** 115 | * Get statuses available for this model. 116 | * 117 | * @return array 118 | */ 119 | public static function getStatuses() 120 | { 121 | return static::statusesClass()::toValues(); 122 | } 123 | 124 | /** 125 | * Set status by label(s) to key and perform a save. 126 | * 127 | * @param array|string|\Spatie\Enum\Enum $name 128 | * 129 | * @return bool 130 | */ 131 | public function setStatus($name = null) 132 | { 133 | if (is_array($name) && !empty($name) && Arr::isAssoc($name)) { 134 | return $this->setStatusWhen(array_key_first($name), head($name)); 135 | } 136 | 137 | if (empty($name) || is_null($name) || $this->hasStatus($name)) { 138 | return false; 139 | } 140 | 141 | $this->setStatusAttribute($name); 142 | 143 | if (!$this->savingStatus) { 144 | return false; 145 | } 146 | 147 | return $this->save(); 148 | } 149 | 150 | /** 151 | * Set status when current status is. 152 | * 153 | * @param mixed $current 154 | * @param mixed $new 155 | * 156 | * @return bool 157 | */ 158 | public function setStatusWhen($current, $new) 159 | { 160 | if (!$this->hasStatus($current) || $this->hasStatus($new)) { 161 | return false; 162 | } 163 | 164 | $this->setStatusAttribute($new); 165 | 166 | return $this->save(); 167 | } 168 | 169 | /** 170 | * Set status relation as attribute. 171 | * 172 | * @param string|\Spatie\Enum\Enum $value 173 | * 174 | * @return void 175 | */ 176 | public function setStatusAttribute($value = null) 177 | { 178 | if (!$value) { 179 | return; 180 | } 181 | 182 | $eventName = $this->formatStatusName($value instanceof Enum ? $value->value : $value); 183 | 184 | $this->savingStatus = $this->fireModelEvent("saving${eventName}") !== false; 185 | 186 | $this->status()->associate( 187 | $this->status()->getModel()::getFromEnum($this->toStatusEnum($value), $this) 188 | ); 189 | } 190 | 191 | /** 192 | * Check current status is equals to. 193 | * 194 | * @param string|array|\Spatie\Enum\Enum $value 195 | * 196 | * @return bool 197 | */ 198 | public function hasStatus($value) 199 | { 200 | $enumFromStatusInstance = $this->toStatusEnum( 201 | $this->getStatus() ?: $this->getDefaultStatus() 202 | ); 203 | 204 | return Collection::make((array) $value)->map(function ($item) { 205 | return $this->toStatusEnum($item); 206 | })->filter()->every(function ($item) use ($enumFromStatusInstance) { 207 | return $enumFromStatusInstance->equals($item); 208 | }); 209 | } 210 | 211 | /** 212 | * Transform status string value to enum object. 213 | * 214 | * @param mixed $value 215 | * 216 | * @return \Spatie\Enum\Enum|false 217 | */ 218 | protected function toStatusEnum($value) 219 | { 220 | /** @var \SkoreLabs\LaravelStatus\Status $statusModel */ 221 | $statusModel = $this->status()->getModel(); 222 | 223 | return $statusModel::toEnum( 224 | static::statusesClass(), 225 | is_string($value) ? Str::camel($value) : $value 226 | ); 227 | } 228 | 229 | /** 230 | * Get model status or default instead. 231 | * 232 | * @param string $column 233 | * 234 | * @return string|null 235 | */ 236 | public function getStatus($column = 'name') 237 | { 238 | return $this->status()->value($column); 239 | } 240 | 241 | /** 242 | * Get default status for this model. 243 | * 244 | * @param string|array $column 245 | * 246 | * @return \Illuminate\Database\Eloquent\Model|object|\Illuminate\Database\Eloquent\Builder|null|mixed 247 | */ 248 | public static function getDefaultStatus($column = 'name') 249 | { 250 | $modelInstance = new static(); 251 | 252 | return $modelInstance->status()->getModel() 253 | ->query() 254 | ->defaultFrom($modelInstance) 255 | ->value($column); 256 | } 257 | 258 | /** 259 | * List all resources of a specified status. 260 | * 261 | * @param \Illuminate\Database\Eloquent\Builder $query 262 | * @param string|\Spatie\Enum\Enum $name 263 | * 264 | * @return \Illuminate\Database\Eloquent\Builder 265 | */ 266 | public function scopeStatus(Builder $query, $name) 267 | { 268 | return $query->whereHas('status', function (Builder $query) use ($name) { 269 | $query->where('name', 'like', $name instanceof Enum ? $name->label : $name); 270 | }); 271 | } 272 | 273 | /** 274 | * List all resources of an array of statuses. 275 | * 276 | * @param \Illuminate\Database\Eloquent\Builder $query 277 | * @param array|array<\Spatie\Enum\Enum> $statuses 278 | * 279 | * @return void 280 | */ 281 | public function scopeStatuses(Builder $query, array $statuses) 282 | { 283 | return $query->whereHas('status', function (Builder $query) use ($statuses) { 284 | $i = 0; 285 | 286 | foreach ($statuses as $status) { 287 | $query->where('name', 'like', $status instanceof Enum ? $status->label : $status, $i === 0 ? 'and' : 'or'); 288 | $i++; 289 | } 290 | }); 291 | } 292 | 293 | /** 294 | * Get status name capitalised. 295 | * 296 | * @param string|array|null $name 297 | * 298 | * @return string|string[]|false 299 | */ 300 | protected function formatStatusName($name = null) 301 | { 302 | if (!$name) { 303 | return false; 304 | } 305 | 306 | $replaceStrFn = static function ($name) { 307 | return str_replace(' ', '', ucwords($name)); 308 | }; 309 | 310 | return is_array($name) ? array_map($replaceStrFn, $name) : $replaceStrFn($name); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /tests/Fixtures/AnotherStatuses.php: -------------------------------------------------------------------------------- 1 | status->name) 50 | ->equals(static::statusesClass()::published()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Fixtures/PostStatuses.php: -------------------------------------------------------------------------------- 1 | PostStatuses::toValues(), 25 | ])->each(function ($names, $modelType) { 26 | foreach ($names as $name) { 27 | Status::create([ 28 | 'name' => $name, 29 | 'model_type' => $modelType, 30 | 'is_default' => array_search($name, $names, true) === 0, 31 | ]); 32 | } 33 | }); 34 | } 35 | 36 | public function test_status_assignment() 37 | { 38 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 39 | $post = Post::make()->forceFill([ 40 | 'title' => $this->faker->words(3, true), 41 | 'content' => $this->faker->paragraph(), 42 | ]); 43 | 44 | $post->status = 'published'; 45 | $post->save(); 46 | 47 | $this->assertFalse($post->isDirty(), 'Model::status($value) should associate + save (persisting)'); 48 | 49 | $this->assertEquals( 50 | PostStatuses::published()->label, 51 | $post->status->name, 52 | 'Model status should be the one previously assigned: "published"' 53 | ); 54 | 55 | $this->assertTrue( 56 | $post->hasStatus('pUbliShed') && $post->status(['pUbLished']), 57 | 'Model::hasStatus($name) & Model::status([$name, ...]) methods should work' 58 | ); 59 | 60 | $this->assertFalse( 61 | $post->hasStatus(['draft', 'archiVed']), 62 | 'Model::hasStatus([$name, ...]) method shouldn\'t return true when array of statuses doesn\'t match the current "published"' 63 | ); 64 | 65 | $this->assertTrue( 66 | $post->hasStatus(PostStatuses::published()), 67 | 'Model::hasStatus($enum) method should also work with enums' 68 | ); 69 | } 70 | 71 | public function test_status_conditional_assignment() 72 | { 73 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 74 | $post = Post::make()->forceFill([ 75 | 'title' => $this->faker->words(3, true), 76 | 'content' => $this->faker->paragraph(), 77 | ]); 78 | 79 | $post->status = 'published'; 80 | $post->save(); 81 | 82 | $this->assertEquals(PostStatuses::published(), $post->status->name); 83 | 84 | $this->assertFalse($post->status(['archived' => 'draft'])); 85 | 86 | $this->assertEquals(PostStatuses::published(), $post->status->name); 87 | 88 | $this->assertTrue($post->status(['published' => 'draft'])); 89 | 90 | $this->assertEquals(PostStatuses::draft(), $post->status->name); 91 | } 92 | 93 | public function test_model_status_default_assignation() 94 | { 95 | $this->markTestIncomplete('How can we make this working?'); 96 | 97 | Event::fake(); 98 | 99 | Event::assertListening( 100 | StatusCreating::class, 101 | AttachDefaultStatus::class 102 | ); 103 | 104 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 105 | $post = Post::make()->forceFill([ 106 | 'title' => $this->faker->words(3, true), 107 | 'content' => $this->faker->paragraph(), 108 | ]); 109 | 110 | $this->assertEmpty( 111 | optional($post->status)->name, 112 | 'When model isn\'t created yet, shouldn\'t have a status' 113 | ); 114 | 115 | $post->save(); 116 | 117 | Event::assertDispatched(StatusCreating::class, function (StatusCreating $event) use ($post) { 118 | return $event->model->is($post) 119 | && $event->model->getDefaultStatus() === optional($event->model->status)->name; 120 | }); 121 | } 122 | 123 | public function test_status_model_methods() 124 | { 125 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 126 | $post = Post::make()->forceFill([ 127 | 'title' => $this->faker->words(3, true), 128 | 'content' => $this->faker->paragraph(), 129 | ]); 130 | 131 | $this->assertEquals( 132 | PostStatuses::draft(), 133 | Status::defaultFrom($post)->value('name'), 134 | 'Status::defaultFrom($model) should get the default status for $model which is "draft"' 135 | ); 136 | 137 | // TODO: Remove this once is fully deprecated 138 | $this->assertEquals( 139 | PostStatuses::draft(), 140 | Status::getDefault($post->getMorphClass(), 'name'), 141 | 'Status::defaultFrom($model) should get the default status for $model which is "draft"' 142 | ); 143 | 144 | $this->assertEquals( 145 | PostStatuses::draft(), 146 | Status::getFromEnum(AnotherStatuses::abc(), $post, 'name'), 147 | 'Status::getFromEnum($enum, $model, \'name\') when enum & model instances doesn\'t match must return "draft"' 148 | ); 149 | 150 | $post->setStatus(PostStatuses::published()); 151 | 152 | $this->assertEquals( 153 | PostStatuses::published()->value, 154 | Status::getFromEnum(PostStatuses::published(), $post, 'name'), 155 | 'Status::getFromEnum($enum, $model, \'name\') published from posts must return "published"' 156 | ); 157 | 158 | $this->assertTrue( 159 | Status::where('name', (string) PostStatuses::published())->first(['*'])->is( 160 | Status::getFromEnum(PostStatuses::published(), $post, ['*']) 161 | ), 162 | 'Status::getFromEnum($enum, $model, [\'*\']) must return a whole "Status" model result' 163 | ); 164 | } 165 | 166 | public function test_status_to_enum() 167 | { 168 | $this->assertEquals( 169 | Post::statusesClass()::published(), 170 | Status::toEnum(Post::statusesClass(), 'published') 171 | ); 172 | 173 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 174 | $post = Post::make()->forceFill([ 175 | 'title' => $this->faker->words(3, true), 176 | 'content' => $this->faker->paragraph(), 177 | ]); 178 | 179 | $mock = $this->mock(Post::class)->shouldAllowMockingProtectedMethods(); 180 | 181 | $mock->shouldReceive('toStatusEnum') 182 | ->andReturn(PostStatuses::published()); 183 | 184 | $post->setStatus('published'); 185 | 186 | $this->assertEquals(PostStatuses::published()->label, $post->getStatus()); 187 | 188 | $mock->shouldReceive('toStatusEnum') 189 | ->withSomeOfArgs($post->getStatus(), 'draft', 'archived') 190 | ->andReturnValues([PostStatuses::published(), PostStatuses::draft(), PostStatuses::archived()]); 191 | 192 | $this->assertFalse($post->hasStatus(['draft', 'archived'])); 193 | } 194 | 195 | public function test_status_assignments_with_enums() 196 | { 197 | /** @var \SkoreLabs\LaravelStatus\Tests\Fixtures\Post $post */ 198 | $post = Post::make()->forceFill([ 199 | 'title' => $this->faker->words(3, true), 200 | 'content' => $this->faker->paragraph(), 201 | ]); 202 | 203 | $post->setStatus(PostStatuses::archived()); 204 | 205 | $this->assertFalse($post->isDirty()); 206 | 207 | $this->assertEquals( 208 | PostStatuses::archived()->label, 209 | $post->status->name 210 | ); 211 | 212 | $post->setStatusWhen(PostStatuses::draft(), PostStatuses::published()); 213 | 214 | $this->assertEquals( 215 | PostStatuses::archived()->label, 216 | $post->status->name 217 | ); 218 | 219 | $post->setStatusWhen(PostStatuses::archived(), PostStatuses::published()); 220 | 221 | $this->assertEquals( 222 | PostStatuses::published()->label, 223 | $post->status->name 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | up(); 22 | } 23 | 24 | /** 25 | * Load package service provider. 26 | * 27 | * @param \Illuminate\Foundation\Application $app 28 | * 29 | * @return array 30 | */ 31 | protected function getPackageProviders($app) 32 | { 33 | return [ 34 | 'SkoreLabs\LaravelStatus\ServiceProvider', 35 | ]; 36 | } 37 | 38 | /** 39 | * Define environment setup. 40 | * 41 | * @param \Illuminate\Foundation\Application $app 42 | * 43 | * @return void 44 | */ 45 | protected function defineEnvironment($app) 46 | { 47 | // Setup default database to use sqlite :memory: 48 | $app['config']->set('database.default', 'testing'); 49 | $app['config']->set('database.connections.testing', [ 50 | 'driver' => 'sqlite', 51 | 'database' => ':memory:', 52 | 'prefix' => '', 53 | ]); 54 | 55 | // Setup package own config (statuses) 56 | $app['config']->set('status', include_once __DIR__.'/../config/status.php'); 57 | } 58 | 59 | /** 60 | * Define database migrations. 61 | * 62 | * @return void 63 | */ 64 | protected function defineDatabaseMigrations() 65 | { 66 | $this->loadMigrationsFrom(__DIR__.'/database'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/database/0000_00_00_000000_create_posts_test_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 13 | $table->unsignedBigInteger('status_id')->nullable(); 14 | $table->string('title'); 15 | $table->text('content'); 16 | $table->timestamps(); 17 | }); 18 | 19 | Schema::table('posts', function (Blueprint $table) { 20 | $table->foreign('status_id') 21 | ->references('id') 22 | ->on('statuses') 23 | ->onDelete('cascade'); 24 | }); 25 | } 26 | 27 | public function down() 28 | { 29 | Schema::dropIfExists('posts'); 30 | } 31 | } 32 | --------------------------------------------------------------------------------