Skip to content

Instantly share code, notes, and snippets.

@kimulisiraj
Forked from iben12/1_Laravel_state-machine.md
Created November 10, 2017 12:00
Show Gist options
  • Select an option

  • Save kimulisiraj/c858f2beccfd67e98e083fcfcc1f4be9 to your computer and use it in GitHub Desktop.

Select an option

Save kimulisiraj/c858f2beccfd67e98e083fcfcc1f4be9 to your computer and use it in GitHub Desktop.
Laravel: State-machine on Eloquent Model
<?php
// database/migrations/yyyy_mm_dd_hhmmss_add_last_state_to_orders_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddLastStateToOrdersTable extends Migration
{
public function up()
{
Schema::table('orders', function($table) {
$table->string('last_state');
});
}
public function down()
{
Schema::table('oders', function($table) {
$table->dropColumn('last_state');
});
}
}
<?php
// database/migrations/yyyy_mm_dd_hhmmss_create_order_states_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateOrderStatesTable extends Migration
{
public function up()
{
Schema::create('order_states', function (Blueprint $table) {
$table->increments('id');
$table->string('order_id');
$table->string('transition');
$table->string('to');
$table->integer('user_id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('order_states');
}
}
<?php
// app/Order.php
namespace App;
use Traits\Statable;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use Statable;
const HISTORY_MODEL = 'App\OrderState'; // the related model to store the history
const SM_CONFIG = 'order'; // the SM graph to use
// other relations and methods of the model
}
<?php
// app/OrderState.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class OrderState extends Model
{
protected $fillable = ['transition','from','user_id','order_id','to'];
public function order() {
return $this->belongsTo('App\Order');
}
public function user() {
return $this->belongsTo('App\User');
}
}
<?php
// app/Traits/Statable.php
namespace App\Traits
use SM\Factory\FactoryInterface;
trait Statable
{
/**
* @var StateMachine $stateMachine
*/
protected $stateMachine;
public function getStateMachine()
{
if (!$this->stateMachine) {
$this->stateMachine = app(FactoryInterface::class)->get($this, self::SM_CONFIG);
}
return $this->stateMachine;
}
public function state($transition = null)
{
if ($transition) {
return $this->getStateMachine()->apply($transition);
}
else {
return $this->getStateMachine()->getState();
}
}
public function transitionAllowed($transition)
{
return $this->getStateMachine()->can($transition);
}
public function history()
{
return $this->hasMany(self::HISTORY_MODEL);
}
}
<?php
// tests/Unit/StatableOrderTest.php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Order;
use App\User;
use SM\SMException;
use Illuminate\Support\Facades\Auth;
class StatebleOrderTest extends TestCase
{
protected $order;
protected function setUp()
{
parent::setup();
$this->order = factory(Order::class)->create([
"user_id" => factory(User::class)->create()->id
]);
Auth::login(User::find($this->order->user->id));
}
public function testCreation()
{
$this->assertInstanceOf('SM\StateMachine\StateMachine', $this->order->getStateMachine());
}
public function testGetState()
{
$this->assertEquals('new', $this->order->state());
}
public function testApplyState()
{
$this->order->state('process');
$this->assertEquals('processed', $this->order->state());
$this->assertEquals(1,$this->order->history()->count());
$this->order->state('ship');
$this->assertEquals('shipped', $this->order->state());
$this->assertEquals(2,$this->order->history()->count());
}
public function testCanTransitState()
{
$this->assertTrue($this->order->transitionAllowed('process'));
$this->assertFalse($this->order->transitionAllowed('ship'));
}
public function testInvalidTransition()
{
$this->expectException(SMException::class);
$this->order->state('ship');
}
}
<?php
// config/state_machine.php
return [
'order' => [
'class' => App\Order::class,
'property_path' => 'last_state',
'states' => [
'new',
'processed',
'cancelled',
'shipped',
'delivered',
'returned'
],
'transitions' => [
'process' => [
'from' => ['new'],
'to' => 'processed'
],
'cancel' => [
'from' => ['new','processed'],
'to' => 'cancelled'
],
'ship' => [
'from' => ['processed'],
'to' => 'shipped'
],
'deliver' => [
'from' => ['shipped'],
'to' => 'delivered'
],
'return' => [
'from' => ['delivered'],
'to' => 'returned'
]
]
],
//...
]
<?php
// app/Listeners/StateHistoryManager.php
namespace App\Listeners;
use SM\Event\TransitionEvent;
class StateHistroyManager
{
public function postTransition(TransitionEvent $event)
{
$sm = $event->getStateMachine();
$model = $sm->getObject();
$model->history()->create([
"transition" => $event->getTransition(),
"to" => $sm->getState(),
"user_id" => auth()->id()
]);
$model->save();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment