Skip to content

Commit b77372e

Browse files
Maksym_OdanetsMaksym_Odanets
Maksym_Odanets
authored and
Maksym_Odanets
committed
1 parent 44e3488 commit b77372e

File tree

8 files changed

+202
-48
lines changed

8 files changed

+202
-48
lines changed

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
}
1717
],
1818
"require": {
19-
"php": "^7.3|^7.4|^8.0",
20-
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
19+
"php": "^8.2|^8.3",
20+
"illuminate/support": "^10.0|^11.0",
2121
"javoscript/laravel-macroable-models": "^1.0"
2222
},
2323
"require-dev": {

phpunit.xml.dist.bak

-25
This file was deleted.

src/StateMachines/State.php

+13-8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function __construct($state, $stateMachine)
2727

2828
public function state()
2929
{
30-
return $this->state;
30+
return $this->normalizeCasting($this->state);
3131
}
3232

3333
public function stateMachine()
@@ -37,7 +37,7 @@ public function stateMachine()
3737

3838
public function is($state)
3939
{
40-
return $this->state === $state;
40+
return $this->state() === $this->normalizeCasting($state);
4141
}
4242

4343
public function isNot($state)
@@ -77,7 +77,7 @@ public function history()
7777

7878
public function canBe($state)
7979
{
80-
return $this->stateMachine->canBe($from = $this->state, $to = $state);
80+
return $this->stateMachine->canBe($from = $this->state(), $to = $this->normalizeCasting($state));
8181
}
8282

8383
public function pendingTransitions()
@@ -90,11 +90,16 @@ public function hasPendingTransitions()
9090
return $this->stateMachine->hasPendingTransitions();
9191
}
9292

93+
public function normalizeCasting($state)
94+
{
95+
return $this->stateMachine->normalizeCasting($state);
96+
}
97+
9398
public function transitionTo($state, $customProperties = [], $responsible = null)
9499
{
95100
$this->stateMachine->transitionTo(
96-
$from = $this->state,
97-
$to = $state,
101+
$from = $this->state(),
102+
$to = $this->normalizeCasting($state),
98103
$customProperties,
99104
$responsible
100105
);
@@ -111,8 +116,8 @@ public function transitionTo($state, $customProperties = [], $responsible = null
111116
public function postponeTransitionTo($state, Carbon $when, $customProperties = [], $responsible = null) : ?PendingTransition
112117
{
113118
return $this->stateMachine->postponeTransitionTo(
114-
$from = $this->state,
115-
$to = $state,
119+
$from = $this->state(),
120+
$to = $this->normalizeCasting($state),
116121
$when,
117122
$customProperties,
118123
$responsible
@@ -121,7 +126,7 @@ public function postponeTransitionTo($state, Carbon $when, $customProperties = [
121126

122127
public function latest() : ?StateHistory
123128
{
124-
return $this->snapshotWhen($this->state);
129+
return $this->snapshotWhen($this->state());
125130
}
126131

127132
public function getCustomProperty($key)

src/StateMachines/StateMachine.php

+26-13
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
use Asantibanez\LaravelEloquentStateMachines\Models\StateHistory;
1010
use Carbon\Carbon;
1111
use Illuminate\Contracts\Validation\Validator;
12-
use Illuminate\Database\Eloquent\Model;
13-
use Illuminate\Support\Arr;
1412
use Illuminate\Support\Collection;
1513
use Illuminate\Validation\ValidationException;
14+
use UnitEnum;
1615

1716
abstract class StateMachine
1817
{
@@ -30,7 +29,7 @@ public function currentState()
3029
{
3130
$field = $this->field;
3231

33-
return $this->model->$field;
32+
return $this->normalizeCasting($this->model->$field);
3433
}
3534

3635
public function history()
@@ -48,7 +47,7 @@ public function timesWas($state)
4847
return $this->history()->to($state)->count();
4948
}
5049

51-
public function whenWas($state) : ?Carbon
50+
public function whenWas($state): ?Carbon
5251
{
5352
$stateHistory = $this->snapshotWhen($state);
5453

@@ -59,12 +58,12 @@ public function whenWas($state) : ?Carbon
5958
return $stateHistory->created_at;
6059
}
6160

62-
public function snapshotWhen($state) : ?StateHistory
61+
public function snapshotWhen($state): ?StateHistory
6362
{
6463
return $this->history()->to($state)->latest('id')->first();
6564
}
6665

67-
public function snapshotsWhen($state) : Collection
66+
public function snapshotsWhen($state): Collection
6867
{
6968
return $this->history()->to($state)->get();
7069
}
@@ -73,7 +72,9 @@ public function canBe($from, $to)
7372
{
7473
$availableTransitions = $this->transitions()[$from] ?? [];
7574

76-
return collect($availableTransitions)->contains($to);
75+
return collect($availableTransitions)->map(function ($state) {
76+
return $this->normalizeCasting($state);
77+
})->contains($to);
7778
}
7879

7980
public function pendingTransitions()
@@ -86,6 +87,11 @@ public function hasPendingTransitions()
8687
return $this->pendingTransitions()->notApplied()->exists();
8788
}
8889

90+
public function normalizeCasting($state)
91+
{
92+
return $state instanceof UnitEnum ? $state->value : $state;
93+
}
94+
8995
/**
9096
* @param $from
9197
* @param $to
@@ -96,6 +102,9 @@ public function hasPendingTransitions()
96102
*/
97103
public function transitionTo($from, $to, $customProperties = [], $responsible = null)
98104
{
105+
$from = $this->normalizeCasting($from);
106+
$to = $this->normalizeCasting($to);
107+
99108
if ($to === $this->currentState()) {
100109
return;
101110
}
@@ -148,8 +157,11 @@ public function transitionTo($from, $to, $customProperties = [], $responsible =
148157
* @return null|PendingTransition
149158
* @throws TransitionNotAllowedException
150159
*/
151-
public function postponeTransitionTo($from, $to, Carbon $when, $customProperties = [], $responsible = null) : ?PendingTransition
160+
public function postponeTransitionTo($from, $to, Carbon $when, $customProperties = [], $responsible = null): ?PendingTransition
152161
{
162+
$from = $this->normalizeCasting($from);
163+
$to = $this->normalizeCasting($to);
164+
153165
if ($to === $this->currentState()) {
154166
return null;
155167
}
@@ -175,23 +187,24 @@ public function cancelAllPendingTransitions()
175187
$this->pendingTransitions()->delete();
176188
}
177189

178-
abstract public function transitions() : array;
190+
abstract public function transitions(): array;
179191

180-
abstract public function defaultState() : ?string;
192+
abstract public function defaultState(): ?string;
181193

182-
abstract public function recordHistory() : bool;
194+
abstract public function recordHistory(): bool;
183195

184196
public function validatorForTransition($from, $to, $model): ?Validator
185197
{
186198
return null;
187199
}
188200

189-
public function afterTransitionHooks() : array
201+
public function afterTransitionHooks(): array
190202
{
191203
return [];
192204
}
193205

194-
public function beforeTransitionHooks() : array {
206+
public function beforeTransitionHooks(): array
207+
{
195208
return [];
196209
}
197210
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Asantibanez\LaravelEloquentStateMachines\Tests\Feature;
4+
5+
use Asantibanez\LaravelEloquentStateMachines\Models\PendingTransition;
6+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestCase;
7+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestEnums\StatusEnum;
8+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestModels\SalesOrderWithEnumCasting;
9+
use Carbon\Carbon;
10+
use Illuminate\Foundation\Testing\RefreshDatabase;
11+
use Illuminate\Foundation\Testing\WithFaker;
12+
13+
class EnumCastingTransitionTest extends TestCase
14+
{
15+
use RefreshDatabase;
16+
use WithFaker;
17+
18+
/** @test */
19+
public function can_transition_to_any_state_using_enum_casting()
20+
{
21+
//Arrange
22+
$salesOrder = SalesOrderWithEnumCasting::create();
23+
24+
$this->assertTrue($salesOrder->status()->is(StatusEnum::PENDING));
25+
26+
$this->assertEquals(StatusEnum::PENDING, $salesOrder->status);
27+
28+
//Act
29+
$salesOrder->status()->transitionTo(StatusEnum::APPROVED);
30+
31+
//Assert
32+
$salesOrder->refresh();
33+
34+
$this->assertTrue($salesOrder->status()->is(StatusEnum::APPROVED));
35+
36+
$this->assertEquals(StatusEnum::APPROVED, $salesOrder->status);
37+
}
38+
39+
/** @test */
40+
public function can_postpone_transition_to_any_state_using_enum_casting()
41+
{
42+
//Arrange
43+
$salesOrder = SalesOrderWithEnumCasting::create();
44+
45+
$this->assertTrue($salesOrder->status()->is(StatusEnum::PENDING));
46+
47+
$this->assertEquals(StatusEnum::PENDING, $salesOrder->status);
48+
49+
//Act
50+
$pendingTransition = $salesOrder->status()->postponeTransitionTo(StatusEnum::APPROVED, Carbon::tomorrow()->startOfDay());
51+
52+
//Assert
53+
$this->assertNotNull($pendingTransition);
54+
55+
$salesOrder->refresh();
56+
57+
$this->assertTrue($salesOrder->status()->is('pending'));
58+
59+
$this->assertTrue($salesOrder->status()->hasPendingTransitions());
60+
61+
/** @var PendingTransition $pendingTransition */
62+
$pendingTransition = $salesOrder->status()->pendingTransitions()->first();
63+
64+
$this->assertEquals('status', $pendingTransition->field);
65+
$this->assertEquals('pending', $pendingTransition->from);
66+
$this->assertEquals('approved', $pendingTransition->to);
67+
68+
$this->assertEquals(Carbon::tomorrow()->startOfDay(), $pendingTransition->transition_at);
69+
70+
$this->assertNull($pendingTransition->applied_at);
71+
72+
$this->assertEquals($salesOrder->id, $pendingTransition->model->id);
73+
}
74+
75+
/** @test */
76+
public function can_access_model_state_history_using_enum_casting()
77+
{
78+
//Arrange
79+
$salesOrder = SalesOrderWithEnumCasting::create();
80+
81+
$this->assertTrue($salesOrder->status()->is(StatusEnum::PENDING));
82+
83+
$this->assertEquals(StatusEnum::PENDING, $salesOrder->status);
84+
85+
//Act
86+
$salesOrder->status()->transitionTo(StatusEnum::APPROVED);
87+
88+
//Assert
89+
$salesOrder->refresh();
90+
91+
$this->assertTrue($salesOrder->status()->is(StatusEnum::APPROVED));
92+
93+
$this->assertEquals(StatusEnum::APPROVED, $salesOrder->status);
94+
95+
$this->assertTrue($salesOrder->status()->was(StatusEnum::PENDING));
96+
}
97+
}

tests/TestEnums/StatusEnum.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestEnums;
4+
5+
enum StatusEnum: string
6+
{
7+
case APPROVED = 'approved';
8+
case CANCELLED = 'cancelled';
9+
case PENDING = 'pending';
10+
case PROCESSED = 'processed';
11+
case WAITING = 'waiting';
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestModels;
4+
5+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestEnums\StatusEnum;
6+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestStateMachines\SalesOrders\StatusWithEnumCastingStateMachine;
7+
use Asantibanez\LaravelEloquentStateMachines\Traits\HasStateMachines;
8+
use Illuminate\Database\Eloquent\Model;
9+
10+
class SalesOrderWithEnumCasting extends Model
11+
{
12+
use HasStateMachines;
13+
14+
protected $table = 'sales_orders';
15+
16+
protected $guarded = [];
17+
18+
protected $casts = [
19+
'status' => StatusEnum::class,
20+
];
21+
22+
public $stateMachines = [
23+
'status' => StatusWithEnumCastingStateMachine::class,
24+
];
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestStateMachines\SalesOrders;
4+
5+
use Asantibanez\LaravelEloquentStateMachines\StateMachines\StateMachine;
6+
use Asantibanez\LaravelEloquentStateMachines\Tests\TestEnums\StatusEnum;
7+
class StatusWithEnumCastingStateMachine extends StateMachine
8+
{
9+
public function recordHistory(): bool
10+
{
11+
return true;
12+
}
13+
14+
public function transitions(): array
15+
{
16+
return [
17+
StatusEnum::PENDING->value => [StatusEnum::APPROVED, StatusEnum::WAITING],
18+
StatusEnum::APPROVED->value => [StatusEnum::PROCESSED],
19+
StatusEnum::WAITING->value => [StatusEnum::CANCELLED],
20+
];
21+
}
22+
23+
public function defaultState(): ?string
24+
{
25+
return StatusEnum::PENDING->value;
26+
}
27+
}

0 commit comments

Comments
 (0)