diff --git a/docs/working-with-states/01-configuring-states.md b/docs/working-with-states/01-configuring-states.md index b605d58..730f842 100644 --- a/docs/working-with-states/01-configuring-states.md +++ b/docs/working-with-states/01-configuring-states.md @@ -111,6 +111,27 @@ abstract class PaymentState extends State } ``` +### Registering custom StateChanged event +By default, when a state is changed, the `StateChanged` event is fired. If you want to use a custom event, you can register it in the `config` method: + +```php +use Spatie\ModelStates\State; +use Spatie\ModelStates\StateConfig; + +use Your\Concrete\State\Event\CustomStateChanged; + +abstract class PaymentState extends State +{ + abstract public function color(): string; + + public static function config(): StateConfig + { + return parent::config() + ->stateChangedEvent(CustomStateChanged::class) + ; + } +} +``` ## Configuring states using attributes If you're using PHP 8 or higher, you can also configure your state using attributes: diff --git a/src/State.php b/src/State.php index febd61c..8e5da58 100644 --- a/src/State.php +++ b/src/State.php @@ -8,6 +8,7 @@ use ReflectionClass; use Spatie\ModelStates\Attributes\AttributeLoader; use Spatie\ModelStates\Events\StateChanged; +use Spatie\ModelStates\Exceptions\ClassDoesNotExtendBaseClass; use Spatie\ModelStates\Exceptions\CouldNotPerformTransition; use Spatie\ModelStates\Exceptions\InvalidConfig; @@ -169,8 +170,9 @@ public function transitionTo($newState, ...$transitionArgs) } /** - * @param Transition $transition + * @param Transition $transition * @return \Illuminate\Database\Eloquent\Model + * @throws ClassDoesNotExtendBaseClass */ public function transition(Transition $transition) { @@ -183,7 +185,13 @@ public function transition(Transition $transition) $model = app()->call([$transition, 'handle']); $model->{$this->field}->setField($this->field); - event(new StateChanged( + $stateChangedEvent = $this->stateConfig->stateChangedEvent; + + if ($stateChangedEvent !== StateChanged::class && get_parent_class($stateChangedEvent) !== StateChanged::class) { + throw ClassDoesNotExtendBaseClass::make($stateChangedEvent, StateChanged::class); + } + + event(new $stateChangedEvent( $this, $model->{$this->field}, $transition, diff --git a/src/StateConfig.php b/src/StateConfig.php index 1ef8c3e..d91623a 100644 --- a/src/StateConfig.php +++ b/src/StateConfig.php @@ -2,6 +2,7 @@ namespace Spatie\ModelStates; +use Spatie\ModelStates\Events\StateChanged; use Spatie\ModelStates\Exceptions\InvalidConfig; class StateConfig @@ -18,6 +19,8 @@ class StateConfig /** @var string[] */ public array $registeredStates = []; + public string $stateChangedEvent = StateChanged::class; + public function __construct( string $baseStateClass ) { @@ -117,6 +120,13 @@ public function registerState($stateClass): StateConfig return $this; } + public function stateChangedEvent(string $event): StateConfig + { + $this->stateChangedEvent = $event; + + return $this; + } + /** * @param string|\Spatie\ModelStates\State $from * @param string|\Spatie\ModelStates\State $to diff --git a/tests/Dummy/CustomEventModelState/CustomEventModelState.php b/tests/Dummy/CustomEventModelState/CustomEventModelState.php new file mode 100644 index 0000000..4b2e7b6 --- /dev/null +++ b/tests/Dummy/CustomEventModelState/CustomEventModelState.php @@ -0,0 +1,17 @@ +default(CustomEventModelStateA::class) + ->allowTransition(CustomEventModelStateA::class, CustomEventModelStateB::class) + ->stateChangedEvent(CustomStateChangedEvent::class); + } +} diff --git a/tests/Dummy/CustomEventModelState/CustomEventModelStateA.php b/tests/Dummy/CustomEventModelState/CustomEventModelStateA.php new file mode 100644 index 0000000..06015e8 --- /dev/null +++ b/tests/Dummy/CustomEventModelState/CustomEventModelStateA.php @@ -0,0 +1,8 @@ +default(CustomInvalidEventModelStateA::class) + ->allowTransition(CustomInvalidEventModelStateA::class, CustomInvalidEventModelStateB::class) + ->stateChangedEvent(CustomInvalidStateChangedEvent::class); + } +} diff --git a/tests/Dummy/CustomEventModelState/CustomInvalidEventModelStateA.php b/tests/Dummy/CustomEventModelState/CustomInvalidEventModelStateA.php new file mode 100644 index 0000000..d954246 --- /dev/null +++ b/tests/Dummy/CustomEventModelState/CustomInvalidEventModelStateA.php @@ -0,0 +1,8 @@ + CustomEventModelState::class, + ]; +} diff --git a/tests/Dummy/TestModelCustomInvalidEvent.php b/tests/Dummy/TestModelCustomInvalidEvent.php new file mode 100644 index 0000000..5eb8285 --- /dev/null +++ b/tests/Dummy/TestModelCustomInvalidEvent.php @@ -0,0 +1,16 @@ + CustomInvalidEventModelState::class, + ]; +} diff --git a/tests/StateTest.php b/tests/StateTest.php index 8e8ab4d..6f63729 100644 --- a/tests/StateTest.php +++ b/tests/StateTest.php @@ -1,6 +1,12 @@ state->transitionTo(CustomEventModelStateB::class); + + Event::assertDispatched(CustomStateChangedEvent::class); +}); + +it('emits the standard state changed event', function () { + Event::fake(); + + $model = TestModel::create(); + + $model->state->transitionTo(StateB::class); + + Event::assertDispatched(StateChanged::class); +}); + +it('should throw exception when custom state changed event does not extend StateChanged', function () { + Event::fake(); + + $model = TestModelCustomInvalidEvent::create(); + + $this->expectException(ClassDoesNotExtendBaseClass::class); + $this->expectExceptionMessage('Class ' . CustomInvalidStateChangedEvent::class . ' does not extend the `' . StateChanged::class . '` base class.'); + + $model->state->transitionTo(CustomInvalidEventModelStateB::class); +});