Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple state models by configuring in state config instead of general config #26

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,12 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
laravel-version:
- ^9.51
- ^10
- ^11
exclude:
- php-version: "8.1"
laravel-version: ^11

steps:
- uses: actions/checkout@v4
Expand All @@ -134,7 +130,6 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
dependencies:
Expand All @@ -144,9 +139,6 @@ jobs:
- ^9.51
- ^10
- ^11
exclude:
- php-version: "8.1"
laravel-version: ^11

services:
mariadb:
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ See [GitHub releases](https://github.com/mll-lab/laravel-utils/releases).

## Unreleased

## v9.0.0

### Changed

- Support multiple `StateManager` classes by configuring the `StateManager`-Model in `StateConfig` and the column name in `StateManager`-Model

### Removed

- Drop support for PHP 8.1

## v8.0.1

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions app/ModelStates/ModelStates/ModelState.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\ModelStates\ModelStates;

use App\ModelStates\StateManager;
use MLL\LaravelUtils\ModelStates\State;
use MLL\LaravelUtils\ModelStates\StateConfig;

Expand All @@ -19,4 +20,9 @@ public static function defaultState(): ModelState
{
return new StateA();
}

public static function stateManagerClass(): string
{
return StateManager::class;
}
}
6 changes: 6 additions & 0 deletions app/ModelStates/OtherModelStates/OtherModelState.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\ModelStates\OtherModelStates;

use App\ModelStates\StateManager;
use App\ModelStates\Transitions\CustomInvalidTransition;
use App\ModelStates\Transitions\CustomTransition;
use MLL\LaravelUtils\ModelStates\State;
Expand All @@ -20,4 +21,9 @@ public static function defaultState(): OtherModelState
{
return new StateX();
}

public static function stateManagerClass(): string
{
return StateManager::class;
}
}
5 changes: 5 additions & 0 deletions app/ModelStates/StateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ public function stateable(): MorphTo
{
return $this->morphTo();
}

public static function stateColumnName(): string
{
return 'state_name';
}
}
4 changes: 2 additions & 2 deletions app/migrations/2022_12_28_012345_state_test_models.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?php declare(strict_types=1);

use App\ModelStates\StateManager;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use MLL\LaravelUtils\ModelStates\ModelStatesServiceProvider;

return new class() extends Migration {
public function up(): void
Expand All @@ -17,7 +17,7 @@ public function up(): void
Schema::create('test.state_managers', function (Blueprint $table): void {
$table->increments('id');
$table->morphs('stateable');
$table->string(ModelStatesServiceProvider::stateColumnName());
$table->string(StateManager::stateColumnName());
$table->timestamps();
});
}
Expand Down
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"source": "https://github.com/mll-lab/laravel-utils"
},
"require": {
"php": "^8.1",
"php": "^8.2",
"illuminate/support": "^9.51 || ^10 || ^11",
"mll-lab/php-utils": "^1.13 || ^2 || ^3 || ^4 || ^5",
"mll-lab/str_putcsv": "^1",
Expand Down Expand Up @@ -72,8 +72,7 @@
"providers": [
"MLL\\LaravelUtils\\LaravelUtilsServiceProvider",
"MLL\\LaravelUtils\\Database\\DatabaseServiceProvider",
"MLL\\LaravelUtils\\Mail\\MailServiceProvider",
"MLL\\LaravelUtils\\ModelStates\\ModelStatesServiceProvider"
"MLL\\LaravelUtils\\Mail\\MailServiceProvider"
]
}
}
Expand Down
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ parameters:
paths:
- src
- tests
checkGenericClassInNonGenericObjectType: false # TODO reenable once we require Laravel 9+
reportUnmatchedIgnoredErrors: false # As long as we support multiple Laravel versions at once, there will be some dead spots
ignoreErrors:
# PHPStan does not believe interfaces can define magic properties
Expand Down
35 changes: 22 additions & 13 deletions src/ModelStates/HasStateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@ abstract public function stateClass(): string;
public static function bootHasStateManager(): void
{
self::created(function (self $self): void {
if (property_exists($self, 'stateManager') && $self->stateManager !== null) {
if (property_exists($self, 'stateManager')
&& isset($self->stateManager)
) {
return;
}

$stateManagerClass = ModelStatesServiceProvider::stateManagerClass();
$stateClass = $self->stateClass();

$stateManagerClass = $stateClass::stateManagerClass();
assert(in_array(IsStateManager::class, class_uses($stateManagerClass)));

$stateManager = new $stateManagerClass();
$stateManager->setAttribute(ModelStatesServiceProvider::stateColumnName(), $self->stateClass()::defaultState()::name());

$defaultState = $stateClass::defaultState();
$stateManager->setAttribute($stateManager::stateColumnName(), $defaultState::name());

$self->stateManager()->save($stateManager);
$self->setRelation('stateManager', $stateManager);
Expand All @@ -28,16 +36,16 @@ public static function bootHasStateManager(): void

public function getStateAttribute(): State
{
$stateClasses = $this->stateClassConfig()->possibleStates();
$stateName = $this->stateManager->getAttribute(ModelStatesServiceProvider::stateColumnName());
$stateName = $this->stateManager->getAttribute($this->stateManager::stateColumnName());
assert(is_string($stateName));

$stateClass = $stateClasses[$stateName] ?? null;
if ($stateClass === null) {
$possibleStateClasses = $this->stateClassConfig()->possibleStates();
$currentStateClass = $possibleStateClasses[$stateName] ?? null;
if ($currentStateClass === null) {
throw new UnknownStateException("The state {$stateName} of {$this->table} with id {$this->id} is not part of {$this->stateClass()}.");
}

return new $stateClass();
return new $currentStateClass();
}

/** @param State|class-string<State> $newState */
Expand All @@ -54,15 +62,16 @@ public function setStateAttribute(State|string $newState): void

public function stateManager(): MorphOne
{
return $this->morphOne(
ModelStatesServiceProvider::stateManagerClass(),
'stateable',
);
$stateClass = $this->stateClass();

return $this->morphOne($stateClass::stateManagerClass(), 'stateable');
}

public function stateClassConfig(): StateConfig
{
return $this->stateClass()::config();
$stateClass = $this->stateClass();

return $stateClass::config();
}

public function stateMachine(): StateMachine
Expand Down
1 change: 1 addition & 0 deletions src/ModelStates/HasStateManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/
interface HasStateManagerInterface
{
/** @phpstan-ignore missingType.generics (declaring type not specified here on purpose because it is an interface) */
public function stateManager(): MorphOne;

/** @return class-string<State> */
Expand Down
2 changes: 2 additions & 0 deletions src/ModelStates/IsStateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ trait IsStateManager
/** @return MorphTo<HasStateManagerInterface&Model, self> */
abstract public function stateable(): MorphTo;

abstract public static function stateColumnName(): string;

/** @return SupportCollection<class-string<State>, State> */
public function getCanTransitionToAttribute(): SupportCollection
{
Expand Down
46 changes: 0 additions & 46 deletions src/ModelStates/ModelStatesServiceProvider.php

This file was deleted.

4 changes: 4 additions & 0 deletions src/ModelStates/State.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace MLL\LaravelUtils\ModelStates;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

abstract class State
Expand All @@ -10,6 +11,9 @@ abstract public static function config(): StateConfig;

abstract public static function defaultState(): self;

/** @return class-string<Model> */
abstract public static function stateManagerClass(): string;

public static function name(): string
{
$stateClassBaseName = (new \ReflectionClass(get_called_class()))->getShortName();
Expand Down
5 changes: 3 additions & 2 deletions src/ModelStates/Transition.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public function from(): State
/** Can be reused in default state transitions. */
final protected function manage(): void
{
$this->model->stateManager->setAttribute(ModelStatesServiceProvider::stateColumnName(), $this->to::name());
$this->model->stateManager->save();
$stateManager = $this->model->stateManager;
$stateManager->setAttribute($stateManager::stateColumnName(), $this->to::name());
$stateManager->save();
}
}
23 changes: 0 additions & 23 deletions src/ModelStates/model-state.php

This file was deleted.

2 changes: 0 additions & 2 deletions tests/DBTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Support\Facades\DB;
use MLL\LaravelUtils\Database\DatabaseServiceProvider;
use MLL\LaravelUtils\ModelStates\ModelStatesServiceProvider;

abstract class DBTestCase extends TestCase
{
Expand Down Expand Up @@ -58,7 +57,6 @@ protected function getPackageProviders($app): array
return [
...parent::getPackageProviders($app),
DatabaseServiceProvider::class,
ModelStatesServiceProvider::class,
];
}

Expand Down