From 04f485fed1136a4bca344321f37a670a3d314aea Mon Sep 17 00:00:00 2001 From: Marcos Barbalho Moreira Date: Thu, 16 Nov 2023 19:55:04 -0300 Subject: [PATCH 1/4] feat: added functionality to work with enum casting feat: added functionality to work with enum casting --- src/StateMachines/State.php | 21 ++-- src/StateMachines/StateMachine.php | 37 ++++--- tests/Feature/EnumCastingTransitionTest.php | 96 +++++++++++++++++++ tests/TestEnums/StatusEnum.php | 13 +++ .../TestModels/SalesOrderWithEnumCasting.php | 25 +++++ .../StatusWithEnumCastingStateMachine.php | 30 ++++++ 6 files changed, 201 insertions(+), 21 deletions(-) create mode 100644 tests/Feature/EnumCastingTransitionTest.php create mode 100644 tests/TestEnums/StatusEnum.php create mode 100644 tests/TestModels/SalesOrderWithEnumCasting.php create mode 100644 tests/TestStateMachines/SalesOrders/StatusWithEnumCastingStateMachine.php diff --git a/src/StateMachines/State.php b/src/StateMachines/State.php index 4d4eb9d..5b5ab08 100644 --- a/src/StateMachines/State.php +++ b/src/StateMachines/State.php @@ -27,7 +27,7 @@ public function __construct($state, $stateMachine) public function state() { - return $this->state; + return $this->normalizeEnumCasting($this->state); } public function stateMachine() @@ -37,7 +37,7 @@ public function stateMachine() public function is($state) { - return $this->state === $state; + return $this->state() === $this->normalizeEnumCasting($state); } public function isNot($state) @@ -77,7 +77,7 @@ public function history() public function canBe($state) { - return $this->stateMachine->canBe($from = $this->state, $to = $state); + return $this->stateMachine->canBe($from = $this->state(), $to = $this->normalizeEnumCasting($state)); } public function pendingTransitions() @@ -90,11 +90,16 @@ public function hasPendingTransitions() return $this->stateMachine->hasPendingTransitions(); } + public function normalizeEnumCasting($state) + { + return $this->stateMachine->normalizeEnumCasting($state); + } + public function transitionTo($state, $customProperties = [], $responsible = null) { $this->stateMachine->transitionTo( - $from = $this->state, - $to = $state, + $from = $this->state(), + $to = $this->normalizeEnumCasting($state), $customProperties, $responsible ); @@ -111,8 +116,8 @@ public function transitionTo($state, $customProperties = [], $responsible = null public function postponeTransitionTo($state, Carbon $when, $customProperties = [], $responsible = null) : ?PendingTransition { return $this->stateMachine->postponeTransitionTo( - $from = $this->state, - $to = $state, + $from = $this->state(), + $to = $this->normalizeEnumCasting($state), $when, $customProperties, $responsible @@ -121,7 +126,7 @@ public function postponeTransitionTo($state, Carbon $when, $customProperties = [ public function latest() : ?StateHistory { - return $this->snapshotWhen($this->state); + return $this->snapshotWhen($this->state()); } public function getCustomProperty($key) diff --git a/src/StateMachines/StateMachine.php b/src/StateMachines/StateMachine.php index 9654f2c..8871484 100644 --- a/src/StateMachines/StateMachine.php +++ b/src/StateMachines/StateMachine.php @@ -9,10 +9,9 @@ use Asantibanez\LaravelEloquentStateMachines\Models\StateHistory; use Carbon\Carbon; use Illuminate\Contracts\Validation\Validator; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Validation\ValidationException; +use UnitEnum; abstract class StateMachine { @@ -30,7 +29,7 @@ public function currentState() { $field = $this->field; - return $this->model->$field; + return $this->normalizeEnumCasting($this->model->$field); } public function history() @@ -48,7 +47,7 @@ public function timesWas($state) return $this->history()->to($state)->count(); } - public function whenWas($state) : ?Carbon + public function whenWas($state): ?Carbon { $stateHistory = $this->snapshotWhen($state); @@ -59,12 +58,12 @@ public function whenWas($state) : ?Carbon return $stateHistory->created_at; } - public function snapshotWhen($state) : ?StateHistory + public function snapshotWhen($state): ?StateHistory { return $this->history()->to($state)->latest('id')->first(); } - public function snapshotsWhen($state) : Collection + public function snapshotsWhen($state): Collection { return $this->history()->to($state)->get(); } @@ -73,7 +72,7 @@ public function canBe($from, $to) { $availableTransitions = $this->transitions()[$from] ?? []; - return collect($availableTransitions)->contains($to); + return collect($availableTransitions)->map(fn ($state) => $this->normalizeEnumCasting($state))->contains($to); } public function pendingTransitions() @@ -86,6 +85,11 @@ public function hasPendingTransitions() return $this->pendingTransitions()->notApplied()->exists(); } + public function normalizeEnumCasting($state) + { + return $state instanceof UnitEnum ? $state->value : $state; + } + /** * @param $from * @param $to @@ -96,6 +100,9 @@ public function hasPendingTransitions() */ public function transitionTo($from, $to, $customProperties = [], $responsible = null) { + $from = $this->normalizeEnumCasting($from); + $to = $this->normalizeEnumCasting($to); + if ($to === $this->currentState()) { return; } @@ -148,8 +155,11 @@ public function transitionTo($from, $to, $customProperties = [], $responsible = * @return null|PendingTransition * @throws TransitionNotAllowedException */ - public function postponeTransitionTo($from, $to, Carbon $when, $customProperties = [], $responsible = null) : ?PendingTransition + public function postponeTransitionTo($from, $to, Carbon $when, $customProperties = [], $responsible = null): ?PendingTransition { + $from = $this->normalizeEnumCasting($from); + $to = $this->normalizeEnumCasting($to); + if ($to === $this->currentState()) { return null; } @@ -175,23 +185,24 @@ public function cancelAllPendingTransitions() $this->pendingTransitions()->delete(); } - abstract public function transitions() : array; + abstract public function transitions(): array; - abstract public function defaultState() : ?string; + abstract public function defaultState(): ?string; - abstract public function recordHistory() : bool; + abstract public function recordHistory(): bool; public function validatorForTransition($from, $to, $model): ?Validator { return null; } - public function afterTransitionHooks() : array + public function afterTransitionHooks(): array { return []; } - public function beforeTransitionHooks() : array { + public function beforeTransitionHooks(): array + { return []; } } diff --git a/tests/Feature/EnumCastingTransitionTest.php b/tests/Feature/EnumCastingTransitionTest.php new file mode 100644 index 0000000..e3829d5 --- /dev/null +++ b/tests/Feature/EnumCastingTransitionTest.php @@ -0,0 +1,96 @@ +assertTrue($salesOrder->status()->is(StatusEnum::PENDING)); + + $this->assertEquals(StatusEnum::PENDING, $salesOrder->status); + + //Act + $salesOrder->status()->transitionTo(StatusEnum::APPROVED); + + //Assert + $salesOrder->refresh(); + + $this->assertTrue($salesOrder->status()->is(StatusEnum::APPROVED)); + + $this->assertEquals(StatusEnum::APPROVED, $salesOrder->status); + } + + /** @test */ + public function can_postpone_transition_to_any_state_using_enum_casting() + { + //Arrange + $salesOrder = SalesOrderWithEnumCasting::create(); + + $this->assertTrue($salesOrder->status()->is(StatusEnum::PENDING)); + + $this->assertEquals(StatusEnum::PENDING, $salesOrder->status); + + //Act + $pendingTransition = $salesOrder->status()->postponeTransitionTo(StatusEnum::APPROVED, Carbon::tomorrow()->startOfDay()); + + //Assert + $this->assertNotNull($pendingTransition); + + $salesOrder->refresh(); + + $this->assertTrue($salesOrder->status()->is('pending')); + + $this->assertTrue($salesOrder->status()->hasPendingTransitions()); + + /** @var PendingTransition $pendingTransition */ + $pendingTransition = $salesOrder->status()->pendingTransitions()->first(); + + $this->assertEquals('status', $pendingTransition->field); + $this->assertEquals('pending', $pendingTransition->from); + $this->assertEquals('approved', $pendingTransition->to); + + $this->assertEquals(Carbon::tomorrow()->startOfDay(), $pendingTransition->transition_at); + + $this->assertNull($pendingTransition->applied_at); + + $this->assertEquals($salesOrder->id, $pendingTransition->model->id); + } + + /** @test */ + public function can_access_model_state_history_using_enum_casting() + { + //Arrange + $salesOrder = SalesOrderWithEnumCasting::create(); + + $this->assertTrue($salesOrder->status()->is(StatusEnum::PENDING)); + + $this->assertEquals(StatusEnum::PENDING, $salesOrder->status); + + //Act + $salesOrder->status()->transitionTo(StatusEnum::APPROVED); + + //Assert + $salesOrder->refresh(); + + $this->assertTrue($salesOrder->status()->is(StatusEnum::APPROVED)); + + $this->assertEquals(StatusEnum::APPROVED, $salesOrder->status); + + $this->assertTrue($salesOrder->status()->was(StatusEnum::PENDING)); + } +} diff --git a/tests/TestEnums/StatusEnum.php b/tests/TestEnums/StatusEnum.php new file mode 100644 index 0000000..561a62f --- /dev/null +++ b/tests/TestEnums/StatusEnum.php @@ -0,0 +1,13 @@ + StatusEnum::class, + ]; + + public $stateMachines = [ + 'status' => StatusWithEnumCastingStateMachine::class, + ]; +} diff --git a/tests/TestStateMachines/SalesOrders/StatusWithEnumCastingStateMachine.php b/tests/TestStateMachines/SalesOrders/StatusWithEnumCastingStateMachine.php new file mode 100644 index 0000000..0eddb83 --- /dev/null +++ b/tests/TestStateMachines/SalesOrders/StatusWithEnumCastingStateMachine.php @@ -0,0 +1,30 @@ +value => [StatusEnum::APPROVED, StatusEnum::WAITING], + StatusEnum::APPROVED->value => [StatusEnum::PROCESSED], + StatusEnum::WAITING->value => [StatusEnum::CANCELLED], + ]; + } + + public function defaultState(): ?string + { + return StatusEnum::PENDING->value; + } +} From fcd0a99e0fceaddfcbc8d8264d658db186b6770b Mon Sep 17 00:00:00 2001 From: Marcos Barbalho Moreira Date: Fri, 15 Dec 2023 11:58:17 -0300 Subject: [PATCH 2/4] chore: updated function name from normalizeEnumCasting to normalizeCasting --- src/StateMachines/State.php | 14 +++++++------- src/StateMachines/StateMachine.php | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/StateMachines/State.php b/src/StateMachines/State.php index 5b5ab08..54e4736 100644 --- a/src/StateMachines/State.php +++ b/src/StateMachines/State.php @@ -27,7 +27,7 @@ public function __construct($state, $stateMachine) public function state() { - return $this->normalizeEnumCasting($this->state); + return $this->normalizeCasting($this->state); } public function stateMachine() @@ -37,7 +37,7 @@ public function stateMachine() public function is($state) { - return $this->state() === $this->normalizeEnumCasting($state); + return $this->state() === $this->normalizeCasting($state); } public function isNot($state) @@ -77,7 +77,7 @@ public function history() public function canBe($state) { - return $this->stateMachine->canBe($from = $this->state(), $to = $this->normalizeEnumCasting($state)); + return $this->stateMachine->canBe($from = $this->state(), $to = $this->normalizeCasting($state)); } public function pendingTransitions() @@ -90,16 +90,16 @@ public function hasPendingTransitions() return $this->stateMachine->hasPendingTransitions(); } - public function normalizeEnumCasting($state) + public function normalizeCasting($state) { - return $this->stateMachine->normalizeEnumCasting($state); + return $this->stateMachine->normalizeCasting($state); } public function transitionTo($state, $customProperties = [], $responsible = null) { $this->stateMachine->transitionTo( $from = $this->state(), - $to = $this->normalizeEnumCasting($state), + $to = $this->normalizeCasting($state), $customProperties, $responsible ); @@ -117,7 +117,7 @@ public function postponeTransitionTo($state, Carbon $when, $customProperties = [ { return $this->stateMachine->postponeTransitionTo( $from = $this->state(), - $to = $this->normalizeEnumCasting($state), + $to = $this->normalizeCasting($state), $when, $customProperties, $responsible diff --git a/src/StateMachines/StateMachine.php b/src/StateMachines/StateMachine.php index 8871484..28ab57a 100644 --- a/src/StateMachines/StateMachine.php +++ b/src/StateMachines/StateMachine.php @@ -29,7 +29,7 @@ public function currentState() { $field = $this->field; - return $this->normalizeEnumCasting($this->model->$field); + return $this->normalizeCasting($this->model->$field); } public function history() @@ -72,7 +72,7 @@ public function canBe($from, $to) { $availableTransitions = $this->transitions()[$from] ?? []; - return collect($availableTransitions)->map(fn ($state) => $this->normalizeEnumCasting($state))->contains($to); + return collect($availableTransitions)->map(fn ($state) => $this->normalizeCasting($state))->contains($to); } public function pendingTransitions() @@ -85,7 +85,7 @@ public function hasPendingTransitions() return $this->pendingTransitions()->notApplied()->exists(); } - public function normalizeEnumCasting($state) + public function normalizeCasting($state) { return $state instanceof UnitEnum ? $state->value : $state; } @@ -100,8 +100,8 @@ public function normalizeEnumCasting($state) */ public function transitionTo($from, $to, $customProperties = [], $responsible = null) { - $from = $this->normalizeEnumCasting($from); - $to = $this->normalizeEnumCasting($to); + $from = $this->normalizeCasting($from); + $to = $this->normalizeCasting($to); if ($to === $this->currentState()) { return; @@ -157,8 +157,8 @@ public function transitionTo($from, $to, $customProperties = [], $responsible = */ public function postponeTransitionTo($from, $to, Carbon $when, $customProperties = [], $responsible = null): ?PendingTransition { - $from = $this->normalizeEnumCasting($from); - $to = $this->normalizeEnumCasting($to); + $from = $this->normalizeCasting($from); + $to = $this->normalizeCasting($to); if ($to === $this->currentState()) { return null; From df81c42fa905460e1eaf1e4c46c047921c8eeda5 Mon Sep 17 00:00:00 2001 From: Marcos Barbalho Moreira Date: Tue, 20 Aug 2024 14:46:58 -0300 Subject: [PATCH 3/4] chore: update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fe942c6..4cff7b1 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.3|^7.4|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "javoscript/laravel-macroable-models": "^1.0" }, "require-dev": { From 9d16260e2ee1c87e3f594b5bc145402f1db590ce Mon Sep 17 00:00:00 2001 From: Marcos Barbalho Moreira Date: Mon, 26 Aug 2024 12:29:21 -0300 Subject: [PATCH 4/4] chore: update arrow function to anonymous function --- src/StateMachines/StateMachine.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/StateMachines/StateMachine.php b/src/StateMachines/StateMachine.php index 28ab57a..7293ac8 100644 --- a/src/StateMachines/StateMachine.php +++ b/src/StateMachines/StateMachine.php @@ -72,7 +72,9 @@ public function canBe($from, $to) { $availableTransitions = $this->transitions()[$from] ?? []; - return collect($availableTransitions)->map(fn ($state) => $this->normalizeCasting($state))->contains($to); + return collect($availableTransitions)->map(function ($state) { + return $this->normalizeCasting($state); + })->contains($to); } public function pendingTransitions()