From b77372e196f201a405b046bcc1dab8fecae71a19 Mon Sep 17 00:00:00 2001 From: Maksym_Odanets Date: Wed, 23 Oct 2024 12:59:57 +0300 Subject: [PATCH] Added https://github.com/asantibanez/laravel-eloquent-state-machines/pull/43 --- composer.json | 4 +- phpunit.xml.dist.bak | 25 ----- src/StateMachines/State.php | 21 ++-- src/StateMachines/StateMachine.php | 39 +++++--- tests/Feature/EnumCastingTransitionTest.php | 97 +++++++++++++++++++ tests/TestEnums/StatusEnum.php | 12 +++ .../TestModels/SalesOrderWithEnumCasting.php | 25 +++++ .../StatusWithEnumCastingStateMachine.php | 27 ++++++ 8 files changed, 202 insertions(+), 48 deletions(-) delete mode 100644 phpunit.xml.dist.bak 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/composer.json b/composer.json index 600a93f..12e4ae1 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ } ], "require": { - "php": "^7.3|^7.4|^8.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "php": "^8.2|^8.3", + "illuminate/support": "^10.0|^11.0", "javoscript/laravel-macroable-models": "^1.0" }, "require-dev": { diff --git a/phpunit.xml.dist.bak b/phpunit.xml.dist.bak deleted file mode 100644 index 938c849..0000000 --- a/phpunit.xml.dist.bak +++ /dev/null @@ -1,25 +0,0 @@ - - - - - tests - - - - - src/ - - - - - - diff --git a/src/StateMachines/State.php b/src/StateMachines/State.php index 4d4eb9d..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->state; + return $this->normalizeCasting($this->state); } public function stateMachine() @@ -37,7 +37,7 @@ public function stateMachine() public function is($state) { - return $this->state === $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 = $state); + return $this->stateMachine->canBe($from = $this->state(), $to = $this->normalizeCasting($state)); } public function pendingTransitions() @@ -90,11 +90,16 @@ public function hasPendingTransitions() return $this->stateMachine->hasPendingTransitions(); } + public function normalizeCasting($state) + { + return $this->stateMachine->normalizeCasting($state); + } + public function transitionTo($state, $customProperties = [], $responsible = null) { $this->stateMachine->transitionTo( - $from = $this->state, - $to = $state, + $from = $this->state(), + $to = $this->normalizeCasting($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->normalizeCasting($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..7293ac8 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->normalizeCasting($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,9 @@ public function canBe($from, $to) { $availableTransitions = $this->transitions()[$from] ?? []; - return collect($availableTransitions)->contains($to); + return collect($availableTransitions)->map(function ($state) { + return $this->normalizeCasting($state); + })->contains($to); } public function pendingTransitions() @@ -86,6 +87,11 @@ public function hasPendingTransitions() return $this->pendingTransitions()->notApplied()->exists(); } + public function normalizeCasting($state) + { + return $state instanceof UnitEnum ? $state->value : $state; + } + /** * @param $from * @param $to @@ -96,6 +102,9 @@ public function hasPendingTransitions() */ public function transitionTo($from, $to, $customProperties = [], $responsible = null) { + $from = $this->normalizeCasting($from); + $to = $this->normalizeCasting($to); + if ($to === $this->currentState()) { return; } @@ -148,8 +157,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->normalizeCasting($from); + $to = $this->normalizeCasting($to); + if ($to === $this->currentState()) { return null; } @@ -175,23 +187,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..7ade08a --- /dev/null +++ b/tests/Feature/EnumCastingTransitionTest.php @@ -0,0 +1,97 @@ +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..2f15881 --- /dev/null +++ b/tests/TestEnums/StatusEnum.php @@ -0,0 +1,12 @@ + 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..96e003c --- /dev/null +++ b/tests/TestStateMachines/SalesOrders/StatusWithEnumCastingStateMachine.php @@ -0,0 +1,27 @@ +value => [StatusEnum::APPROVED, StatusEnum::WAITING], + StatusEnum::APPROVED->value => [StatusEnum::PROCESSED], + StatusEnum::WAITING->value => [StatusEnum::CANCELLED], + ]; + } + + public function defaultState(): ?string + { + return StatusEnum::PENDING->value; + } +}