Skip to content

Commit

Permalink
Merge pull request #15 from asantibanez/features/add-before-transitio…
Browse files Browse the repository at this point in the history
…n-hooks

Add before transition hooks and refactored after transition hooks
  • Loading branch information
asantibanez authored Feb 10, 2021
2 parents a1a45c5 + e770cd5 commit 1868525
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 36 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to `laravel-eloquent-state-machines` will be documented in this file

## v3.0.0 - 2021-02-10

- Added `beforeTransitionHooks`
- **Breaking Change**: Renamed `transitionHooks` to `afterTransitionHooks` and changed arguments for callbacks
- Refactored tests

## v2.3.0 - 2020-01-26

- Added support for PHP 8.
Expand Down
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,24 +377,43 @@ applying the transition.

### Adding Hooks

We can also add custom hooks/callbacks that will be executed once a transition is applied.
To do so, we must override the `transitionHooks()` method in our state machine.
We can also add custom hooks/callbacks that will be executed before/after a transition is applied.
To do so, we must override the `beforeTransitionHooks()` and `afterTransitionHooks()` methods in our state machine
accordingly.

The `transitionHooks()` method must return an keyed array with the state and an array of callbacks/closures
to be executed. Eg.
Both transition hooks methods must return a keyed array with the state and an array of callbacks/closures
to be executed.

> NOTE: The keys for the array must be the `$from` states.
Example

```php
class StatusStateMachine extends StateMachine
{
public function transitionHooks(): array
public function beforeTransitionHooks(): array
{
return [
'approved' => [
function ($to, $model) {
// Dispatch some job BEFORE "approved changes to $to"
},
function ($to, $model) {
// Send mail BEFORE "approved changes to $to"
},
],
];
}

public function afterTransitionHooks(): array
{
return [
'processed' => [
function ($from, $model) {
// Dispatch some job
'approved' => [
function ($to, $model) {
// Dispatch some job AFTER "approved changes to $to"
},
function ($from, $model) {
// Send mail
function ($to, $model) {
// Send mail AFTER "approved changes to $to"
},
],
];
Expand Down
22 changes: 17 additions & 5 deletions src/StateMachines/StateMachine.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ public function transitionTo($from, $to, $customProperties = [], $responsible =
throw new ValidationException($validator);
}

$beforeTransitionHooks = $this->beforeTransitionHooks()[$from] ?? [];

collect($beforeTransitionHooks)
->each(function ($callable) use ($to) {
$callable($to, $this->model);
});

$field = $this->field;
$this->model->$field = $to;
$this->model->save();
Expand All @@ -117,11 +124,11 @@ public function transitionTo($from, $to, $customProperties = [], $responsible =
$this->model->recordState($field, $from, $to, $customProperties, $responsible);
}

$transitionHooks = $this->transitionHooks()[$to] ?? [];
$afterTransitionHooks = $this->afterTransitionHooks()[$from] ?? [];

collect($transitionHooks)
->each(function ($callable) use ($from) {
$callable($from, $this->model);
collect($afterTransitionHooks)
->each(function ($callable) use ($to) {
$callable($to, $this->model);
});

$this->cancelAllPendingTransitions();
Expand Down Expand Up @@ -174,7 +181,12 @@ public function validatorForTransition($from, $to, $model): ?Validator
return null;
}

public function transitionHooks() : array {
public function afterTransitionHooks() : array
{
return [];
}

public function beforeTransitionHooks() : array {
return [];
}
}
65 changes: 65 additions & 0 deletions tests/Feature/AfterTransitionHookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Asantibanez\LaravelEloquentStateMachines\Tests\Feature;

use Asantibanez\LaravelEloquentStateMachines\Tests\TestCase;
use Asantibanez\LaravelEloquentStateMachines\Tests\TestJobs\AfterTransitionJob;
use Asantibanez\LaravelEloquentStateMachines\Tests\TestModels\SalesOrderWithAfterTransitionHook;
use Asantibanez\LaravelEloquentStateMachines\Tests\TestModels\SalesOrderWithBeforeTransitionHook;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Queue;

class AfterTransitionHookTest extends TestCase
{
use RefreshDatabase;
use WithFaker;

/** @test */
public function should_call_after_transition_hooks()
{
//Arrange
Queue::fake();

$salesOrder = SalesOrderWithAfterTransitionHook::create([
'total' => 100,
'notes' => 'before',
]);

//Act
$salesOrder->status()->transitionTo('approved');

//Assert
$salesOrder->refresh();

$this->assertEquals(200, $salesOrder->total);
$this->assertEquals('after', $salesOrder->notes);

Queue::assertPushed(AfterTransitionJob::class);
}

/** @test */
public function should_not_call_after_transition_hooks_if_not_defined()
{
//Arrange
Queue::fake();

$salesOrder = SalesOrderWithAfterTransitionHook::create([
'status' => 'approved'
]);

$this->assertNull($salesOrder->total);
$this->assertNull($salesOrder->notes);

//Act
$salesOrder->status()->transitionTo('processed');

//Assert
$salesOrder->refresh();

$this->assertNull($salesOrder->total);
$this->assertNull($salesOrder->notes);

Queue::assertNotPushed(AfterTransitionJob::class);
}
}
64 changes: 64 additions & 0 deletions tests/Feature/BeforeTransitionHookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Asantibanez\LaravelEloquentStateMachines\Tests\Feature;

use Asantibanez\LaravelEloquentStateMachines\Tests\TestCase;
use Asantibanez\LaravelEloquentStateMachines\Tests\TestJobs\BeforeTransitionJob;
use Asantibanez\LaravelEloquentStateMachines\Tests\TestModels\SalesOrderWithBeforeTransitionHook;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Queue;

class BeforeTransitionHookTest extends TestCase
{
use RefreshDatabase;
use WithFaker;

/** @test */
public function should_call_before_transition_hooks()
{
//Arrange
Queue::fake();

$salesOrder = SalesOrderWithBeforeTransitionHook::create();

$this->assertNull($salesOrder->total);
$this->assertNull($salesOrder->notes);

//Act
$salesOrder->status()->transitionTo('approved');

//Assert
$salesOrder->refresh();

$this->assertEquals(100, $salesOrder->total);
$this->assertEquals('Notes updated', $salesOrder->notes);

Queue::assertPushed(BeforeTransitionJob::class);
}

/** @test */
public function should_not_call_before_transition_hooks_if_not_defined()
{
//Arrange
Queue::fake();

$salesOrder = SalesOrderWithBeforeTransitionHook::create([
'status' => 'approved'
]);

$this->assertNull($salesOrder->total);
$this->assertNull($salesOrder->notes);

//Act
$salesOrder->status()->transitionTo('processed');

//Assert
$salesOrder->refresh();

$this->assertNull($salesOrder->total);
$this->assertNull($salesOrder->notes);

Queue::assertNotPushed(BeforeTransitionJob::class);
}
}
20 changes: 0 additions & 20 deletions tests/Feature/HasStateMachinesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,26 +299,6 @@ public function can_record_history_with_custom_properties_when_transitioning_to_
$this->assertEquals($comments, $salesOrder->status()->getCustomProperty('comments'));
}

/** @test */
public function should_call_transition_hook()
{
Queue::fake();

//Arrange
$salesOrder = factory(SalesOrder::class)->create([
'status' => 'approved',
]);

//Act
$salesOrder->fulfillment()->transitionTo('pending');

//Assert
Queue::assertPushed(StartSalesOrderFulfillmentJob::class, function ($job) use ($salesOrder) {
$this->assertEquals($salesOrder->id, $job->salesOrder->id);
return true;
});
}

/** @test */
public function can_check_if_previous_state_was_transitioned()
{
Expand Down
15 changes: 15 additions & 0 deletions tests/TestJobs/AfterTransitionJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php


namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestJobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class AfterTransitionJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
}
15 changes: 15 additions & 0 deletions tests/TestJobs/BeforeTransitionJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php


namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestJobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class BeforeTransitionJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
}
20 changes: 20 additions & 0 deletions tests/TestModels/SalesOrderWithAfterTransitionHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestModels;

use Asantibanez\LaravelEloquentStateMachines\Tests\TestStateMachines\SalesOrders\StatusWithAfterTransitionHookStateMachine;
use Asantibanez\LaravelEloquentStateMachines\Traits\HasStateMachines;
use Illuminate\Database\Eloquent\Model;

class SalesOrderWithAfterTransitionHook extends Model
{
use HasStateMachines;

protected $table = 'sales_orders';

protected $guarded = [];

public $stateMachines = [
'status' => StatusWithAfterTransitionHookStateMachine::class,
];
}
20 changes: 20 additions & 0 deletions tests/TestModels/SalesOrderWithBeforeTransitionHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Asantibanez\LaravelEloquentStateMachines\Tests\TestModels;

use Asantibanez\LaravelEloquentStateMachines\Tests\TestStateMachines\SalesOrders\StatusWithBeforeTransitionHookStateMachine;
use Asantibanez\LaravelEloquentStateMachines\Traits\HasStateMachines;
use Illuminate\Database\Eloquent\Model;

class SalesOrderWithBeforeTransitionHook extends Model
{
use HasStateMachines;

protected $table = 'sales_orders';

protected $guarded = [];

public $stateMachines = [
'status' => StatusWithBeforeTransitionHookStateMachine::class,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function validatorForTransition($from, $to, $model): ?Validator
return parent::validatorForTransition($from, $to, $model);
}

public function transitionHooks(): array
public function afterTransitionHooks(): array
{
return [
'pending' => [
Expand Down
Loading

0 comments on commit 1868525

Please sign in to comment.