Skip to content

Commit

Permalink
Merge pull request #269 from IlliaVeremiev/feature/allow-transition-a…
Browse files Browse the repository at this point in the history
…rguments-for-custom-default-transition

Feature: Added ...transitionArgs to default transition constructor call to allow arguments use for custom default transition
  • Loading branch information
freekmurze authored Dec 16, 2024
2 parents 8288193 + 152906f commit 627ceb8
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
title: Custom default transition class
weight: 6
---

When working with state transitions, you may need to pass additional contextual data to your `StateChanged` event
listeners. While custom transitions allow this for specific state changes, sometimes you need this functionality for all
transitions. To handle such scenarios `DefaultTransition` class can be extended.

The following example uses different logic depending on how `transitionTo` is called.

Creating custom default transition class:

```php
use Spatie\ModelStates\DefaultTransition;
use Spatie\ModelStates\State;

class CustomDefaultTransitionWithAttributes extends DefaultTransition
{
public function __construct($model, string $field, State $newState, public bool $silent = false)
{
parent::__construct($model, $field, $newState);
}
}
```

Register your custom transition class in `config/model-states.php`:

```php
return [
'default_transition' => CustomDefaultTransitionWithAttributes::class
];
```

Implement your state change listener to use the custom parameter:

```php
use Spatie\ModelStates\Events\StateChanged;

class OrderStateChangedListener
{
public function handle(StateChanged $event): void
{
$isSilent = $event->transition->silent;

$this->processOrderState($event->model);

if (! $isSilent) {
$this->notifyUser($event->model);
}
}
}
```

Now we can pass additional parameter to `transitionTo` method, to omit notification logic:

```php
class OrderService {
public function markAsPaid(Order $order): void
{
// Will trigger notification
$order->state->transitionTo(PaidState::class);
// Also can be specified explicitly
$order->state->transitionTo(PaidState::class, false);
}

public function markAsPaidSilently(Order $order): void
{
// Will not trigger notification
$order->state->transitionTo(PaidState::class, true);
}
}
```

Important notes:

- Custom parameters are only available within the context of the event listeners
- Parameters must be serializable if you plan to queue your state change listeners
3 changes: 2 additions & 1 deletion src/State.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ private function resolveTransitionClass(
$transition = new $defaultTransition(
$this->model,
$this->field,
$newState
$newState,
...$transitionArgs
);
} else {
$transition = new $transitionClass($this->model, ...$transitionArgs);
Expand Down
14 changes: 14 additions & 0 deletions tests/Dummy/Transitions/CustomDefaultTransitionWithAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Spatie\ModelStates\Tests\Dummy\Transitions;

use Spatie\ModelStates\DefaultTransition;
use Spatie\ModelStates\State;

class CustomDefaultTransitionWithAttributes extends DefaultTransition
{
public function __construct($model, string $field, State $newState, public bool $silent = false)
{
parent::__construct($model, $field, $newState);
}
}
18 changes: 18 additions & 0 deletions tests/StateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,24 @@
Event::assertNotDispatched(TestModelUpdatingEvent::class);
});

it('can use attributes with custom default transition', function () {
Event::fake();
$customDefaultTransitionClass = \Spatie\ModelStates\Tests\Dummy\Transitions\CustomDefaultTransitionWithAttributes::class;

config()->set('model-states.default_transition', $customDefaultTransitionClass);

TestModel::create()->state->transitionTo(StateB::class, true);

Event::assertDispatched(
StateChanged::class,
function (StateChanged $event) use ($customDefaultTransitionClass) {
$transition = $event->transition;
return $transition instanceof $customDefaultTransitionClass
&& $transition->silent === true;
}
);
});

it('can emit a custom state changed event', function () {
Event::fake();

Expand Down

0 comments on commit 627ceb8

Please sign in to comment.