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

Add ability to ignore transition to same state #267

Closed
wants to merge 5 commits into from
Closed
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
32 changes: 31 additions & 1 deletion docs/working-with-transitions/01-configuring-transitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,37 @@ Transitions can then be used like so:
$payment->state->transitionTo(Paid::class);
```

This line will only work when a valid transition was configured. If the initial state of `$payment` already was `Paid`, a `\Spatie\ModelStates\Exceptions\TransitionNotFound` will be thrown instead of changing the state.
This line will only work when a valid transition was configured. If the initial state of `$payment` already was `Paid`, a `\Spatie\ModelStates\Exceptions\TransitionNotFound` will be thrown instead of changing the state.

## Ignoring same state transitions

In some cases you may want to handle transition to same state without manually setting `allowTransition`, you can call `ignoreSameState`

Please note that the `StateChanged` event will fire anyway.

```php
abstract class PaymentState extends State
{
// …

public static function config(): StateConfig
{
return parent::config()
->ignoreSameState()
->allowTransition([Created::class, Pending::class], Failed::class, ToFailed::class);
}
}
```

It also works with `IgnoreSameState` Attribute

```php
#[IgnoreSameState]
abstract class PaymentState extends State
{
//...
}
```

## Allow multiple transitions at once

Expand Down
6 changes: 6 additions & 0 deletions src/Attributes/AttributeLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public function load(StateConfig $stateConfig): StateConfig

$stateConfig->default($defaultStateAttribute->defaultStateClass);
}

if ($this->reflectionClass->getAttributes(IgnoreSameState::class)[0] ?? null) {
/** @var \Spatie\ModelStates\Attributes\IgnoreSameState $transitionAttribute */

$stateConfig->ignoreSameState();
}

$registerStateAttributes = $this->reflectionClass->getAttributes(RegisterState::class);

Expand Down
8 changes: 8 additions & 0 deletions src/Attributes/IgnoreSameState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Spatie\ModelStates\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
class IgnoreSameState {}
20 changes: 19 additions & 1 deletion src/StateConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class StateConfig
/** @var string[] */
public array $registeredStates = [];

/** @var bool */
public bool $shouldIgnoreSameState = false;

public string $stateChangedEvent = StateChanged::class;

public function __construct(
Expand All @@ -34,6 +37,13 @@ public function default(string $defaultStateClass): StateConfig
return $this;
}

public function ignoreSameState(): StateConfig
{
$this->shouldIgnoreSameState = true;

return $this;
}

public function allowTransition($from, string $to, ?string $transition = null): StateConfig
{
if (is_array($from)) {
Expand Down Expand Up @@ -72,6 +82,10 @@ public function allowTransitions(array $transitions): StateConfig

public function isTransitionAllowed(string $fromMorphClass, string $toMorphClass): bool
{
if($this->shouldIgnoreSameState && $fromMorphClass === $toMorphClass){
return true;
}

$transitionKey = $this->createTransitionKey($fromMorphClass, $toMorphClass);

return array_key_exists($transitionKey, $this->allowedTransitions);
Expand All @@ -81,7 +95,11 @@ public function resolveTransitionClass(string $fromMorphClass, string $toMorphCl
{
$transitionKey = $this->createTransitionKey($fromMorphClass, $toMorphClass);

return $this->allowedTransitions[$transitionKey];
if(array_key_exists($transitionKey, $this->allowedTransitions)) {
return $this->allowedTransitions[$transitionKey];
}

return null;
}

public function transitionableStates(string $fromMorphClass): array
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;

use Spatie\ModelStates\Attributes\AllowTransition;
use Spatie\ModelStates\Attributes\DefaultState;
use Spatie\ModelStates\Attributes\IgnoreSameState;
use Spatie\ModelStates\State;

#[
DefaultState(IgnoreSameStateModelAttributeStateA::class),
AllowTransition(IgnoreSameStateModelAttributeStateA::class, IgnoreSameStateModelAttributeStateB::class),
IgnoreSameState
]
abstract class IgnoreSameStateModelAttributeState extends State
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;


class IgnoreSameStateModelAttributeStateA extends IgnoreSameStateModelAttributeState
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;

class IgnoreSameStateModelAttributeStateB extends IgnoreSameStateModelAttributeState
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;

use Spatie\ModelStates\State;
use Spatie\ModelStates\StateConfig;

abstract class IgnoreSameStateModelState extends State
{
public static function config(): StateConfig
{
return parent::config()
->ignoreSameState()
->allowTransition(IgnoreSameStateModelStateA::class, IgnoreSameStateModelStateB::class)
->default(IgnoreSameStateModelStateA::class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;

class IgnoreSameStateModelStateA extends IgnoreSameStateModelState
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState;

class IgnoreSameStateModelStateB extends IgnoreSameStateModelState
{
}
12 changes: 12 additions & 0 deletions tests/Dummy/TestModelIgnoresSameState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy;

use Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState\IgnoreSameStateModelState;

class TestModelIgnoresSameState extends TestModel
{
protected $casts = [
'state' => IgnoreSameStateModelState::class,
];
}
12 changes: 12 additions & 0 deletions tests/Dummy/TestModelIgnoresSameStateByAttribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy;

use Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState\IgnoreSameStateModelAttributeState;

class TestModelIgnoresSameStateByAttribute extends TestModel
{
protected $casts = [
'state' => IgnoreSameStateModelAttributeState::class,
];
}
28 changes: 28 additions & 0 deletions tests/TransitionTest.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?php

use Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState\IgnoreSameStateModelAttributeStateA;
use Illuminate\Support\Facades\Event;
use Spatie\ModelStates\DefaultTransition;
use Spatie\ModelStates\Events\StateChanged;
use Spatie\ModelStates\Exceptions\TransitionNotAllowed;
use Spatie\ModelStates\Exceptions\TransitionNotFound;
use Spatie\ModelStates\Tests\Dummy\IgnoreSameStateModelState\IgnoreSameStateModelStateA;
use Spatie\ModelStates\Tests\Dummy\ModelStates\StateA;
use Spatie\ModelStates\Tests\Dummy\ModelStates\StateB;
use Spatie\ModelStates\Tests\Dummy\ModelStates\StateC;
Expand All @@ -13,6 +15,8 @@
use Spatie\ModelStates\Tests\Dummy\OtherModelStates\StateY;
use Spatie\ModelStates\Tests\Dummy\OtherModelStates\StateZ;
use Spatie\ModelStates\Tests\Dummy\TestModel;
use Spatie\ModelStates\Tests\Dummy\TestModelIgnoresSameState;
use Spatie\ModelStates\Tests\Dummy\TestModelIgnoresSameStateByAttribute;
use Spatie\ModelStates\Tests\Dummy\TestModelWithCustomTransition;
use Spatie\ModelStates\Tests\Dummy\TestModelWithTransitionsFromArray;
use Spatie\ModelStates\Tests\Dummy\Transitions\CustomInvalidTransition;
Expand Down Expand Up @@ -171,3 +175,27 @@

expect($model->state)->toBeInstanceOf(StateC::class);
});

it('ignore transition to same state', function(){
$model = TestModelIgnoresSameState::create([
'state' => IgnoreSameStateModelStateA::class
]);

expect($model->state->canTransitionTo(IgnoreSameStateModelStateA::class))->toBeTrue();

$model->state->transitionTo(IgnoreSameStateModelStateA::class);

expect($model->state)->toBeInstanceOf(IgnoreSameStateModelStateA::class);
});

it('ignore transition to same state using Attribute', function(){
$model = TestModelIgnoresSameStateByAttribute::create([
'state' => IgnoreSameStateModelAttributeStateA::class
]);

expect($model->state->canTransitionTo(IgnoreSameStateModelAttributeStateA::class))->toBeTrue();

$model->state->transitionTo(IgnoreSameStateModelAttributeStateA::class);

expect($model->state)->toBeInstanceOf(IgnoreSameStateModelAttributeStateA::class);
});