From a6ab689ff6a894a3fbf4cd71fad0f22c63b00f3c Mon Sep 17 00:00:00 2001 From: fmeccanici Date: Thu, 21 Sep 2023 21:14:22 +0200 Subject: [PATCH 1/2] Allow to set custom StateChanged event and throw exception if custom event does not extend StateChanged class --- src/State.php | 12 +++++- src/StateConfig.php | 10 +++++ .../CustomEventModelState.php | 17 ++++++++ .../CustomEventModelStateA.php | 8 ++++ .../CustomEventModelStateB.php | 8 ++++ .../CustomInvalidEventModelState.php | 17 ++++++++ .../CustomInvalidEventModelStateA.php | 8 ++++ .../CustomInvalidEventModelStateB.php | 8 ++++ .../CustomInvalidStateChangedEvent.php | 12 ++++++ .../CustomStateChangedEvent.php | 12 ++++++ tests/Dummy/TestModelCustomEvent.php | 15 +++++++ tests/Dummy/TestModelCustomInvalidEvent.php | 16 ++++++++ tests/StateTest.php | 39 +++++++++++++++++++ 13 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 tests/Dummy/CustomEventModelState/CustomEventModelState.php create mode 100644 tests/Dummy/CustomEventModelState/CustomEventModelStateA.php create mode 100644 tests/Dummy/CustomEventModelState/CustomEventModelStateB.php create mode 100644 tests/Dummy/CustomEventModelState/CustomInvalidEventModelState.php create mode 100644 tests/Dummy/CustomEventModelState/CustomInvalidEventModelStateA.php create mode 100644 tests/Dummy/CustomEventModelState/CustomInvalidEventModelStateB.php create mode 100644 tests/Dummy/CustomEventModelState/CustomInvalidStateChangedEvent.php create mode 100644 tests/Dummy/CustomEventModelState/CustomStateChangedEvent.php create mode 100644 tests/Dummy/TestModelCustomEvent.php create mode 100644 tests/Dummy/TestModelCustomInvalidEvent.php 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); +}); From a0b5bf69a0fa45b099c100591184805f7833ac9e Mon Sep 17 00:00:00 2001 From: fmeccanici Date: Thu, 21 Sep 2023 21:21:25 +0200 Subject: [PATCH 2/2] Update docs --- .../01-configuring-states.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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: