Skip to content

Commit

Permalink
Throw DuplicateTransitionException on duplicate state transitions i…
Browse files Browse the repository at this point in the history
…n `StateConfig`
  • Loading branch information
spawnia committed Nov 19, 2024
1 parent 2b06657 commit 95cd63a
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ See [GitHub releases](https://github.com/mll-lab/laravel-utils/releases).

## Unreleased

## v8.0.0

### Changed

- Throw `DuplicateTransitionException` on duplicate state transitions in `StateConfig`

## v7.0.0

### Added
Expand Down
11 changes: 11 additions & 0 deletions src/ModelStates/Exceptions/DuplicateTransitionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types=1);

namespace MLL\LaravelUtils\ModelStates\Exceptions;

final class DuplicateTransitionException extends \Exception
{
public function __construct(string $from, string $to)
{
parent::__construct("There is already a transition from {$from} to {$to}.");
}
}
21 changes: 10 additions & 11 deletions src/ModelStates/StateConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection as SupportCollection;
use MLL\LaravelUtils\ModelStates\Exceptions\ClassDoesNotExtendBaseClass;
use MLL\LaravelUtils\ModelStates\Exceptions\DuplicateTransitionException;

final class StateConfig
{
/**
* @var array<
* class-string<State>,
* array<class-string<State>, class-string<Transition>|null>
* class-string<State>,
* array<class-string<State>, class-string<Transition>|null>,
* >
*/
public array $allowedTransitions = [];
Expand All @@ -31,24 +32,22 @@ public function allowTransition(string|array $fromOrFroms, string $to, string $t
return $this;
}

// @phpstan-ignore-next-line php-stan is not right here
// @phpstan-ignore-next-line PHPStan is not right here
if (! is_subclass_of($fromOrFroms, State::class)) {
throw new ClassDoesNotExtendBaseClass($fromOrFroms, State::class);
}

// @phpstan-ignore-next-line php-stan is not right here
// @phpstan-ignore-next-line PHPStan is not right here
if (! is_subclass_of($to, State::class)) {
throw new ClassDoesNotExtendBaseClass($to, State::class);
}

// There might already be transitions registered for this state
$fromTransition = $this->allowedTransitions[$fromOrFroms] ?? [];

// We just overwrite the transition with what was defined.
// We might consider throwing an exception here to catch double configuration.
$fromTransition[$to] = $transition;
if (isset($this->allowedTransitions[$fromOrFroms][$to])) {
throw new DuplicateTransitionException($fromOrFroms, $to);
}

$this->allowedTransitions[$fromOrFroms] = $fromTransition;
$this->allowedTransitions[$fromOrFroms][$to] = $transition;

return $this;
}
Expand Down Expand Up @@ -103,7 +102,7 @@ public function possibleNextStates(State $from): SupportCollection
$next[$stateClass] = new $stateClass();
}

// @phpstan-ignore-next-line php-stan is not right here
// @phpstan-ignore-next-line PHPStan is not right here
return new SupportCollection($next);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ModelStates/StateEnumType.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class StateEnumType extends EnumType
*/
public function __construct(string $stateClass, ?string $name = null)
{
// @phpstan-ignore-next-line php-stan is not right here
// @phpstan-ignore-next-line PHPStan is not right here
if (! is_subclass_of($stateClass, State::class)) {
$abstractState = State::class;
throw new \InvalidArgumentException("Must pass an instance of {$abstractState}, got {$stateClass}.");
Expand Down
11 changes: 11 additions & 0 deletions tests/ModelStates/StateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
use App\ModelStates\Transitions\CustomTransition;
use Illuminate\Support\Collection as SupportCollection;
use MLL\LaravelUtils\ModelStates\DefaultTransition;
use MLL\LaravelUtils\ModelStates\Exceptions\DuplicateTransitionException;
use MLL\LaravelUtils\ModelStates\Exceptions\TransitionNotAllowed;
use MLL\LaravelUtils\ModelStates\Exceptions\TransitionNotFound;
use MLL\LaravelUtils\ModelStates\Exceptions\UnknownStateException;
use MLL\LaravelUtils\ModelStates\MermaidStateConfigValidator;
use MLL\LaravelUtils\ModelStates\StateConfig;
use MLL\LaravelUtils\ModelStates\TransitionDirection;
use MLL\LaravelUtils\Tests\DBTestCase;

Expand Down Expand Up @@ -182,6 +184,15 @@ public function testUnknownStateThrowsExceptionsForRetrieval(): void
$model->state;
}

public function testDuplicateTransitionException(): void
{
$stateConfig = new StateConfig();
$stateConfig->allowTransition(StateA::class, StateB::class);

$this->expectExceptionObject(new DuplicateTransitionException(StateA::class, StateB::class));
$stateConfig->allowTransition(StateA::class, StateB::class);
}

public function testGenerateMermaidGraph(): void
{
$workflowAsMermaidGraph = /** @lang Mermaid */ <<<'MERMAID'
Expand Down

0 comments on commit 95cd63a

Please sign in to comment.