PHP Classes

PHP State Machine One: Process transitions in a state machine

Recommend this page to a friend!
     
  Info   Example   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Ratings Unique User Downloads Download Rankings
Not enough user ratingsTotal: 141 All time: 9,197 This week: 488Up
Version License PHP version Categories
statemachineone 1.0.1GNU Lesser Genera...5Algorithms, PHP 5, Emulators
Description 

Author

This package can process transitions in a state machine.

It can compose a state machine by defining a list of states and conditions applied to state variables that must be true so the transitions between them happen.

The package can also define jobs with initial values of variables, as well run those jobs and performing the transitions between states when the conditions defined before are true.

Innovation Award
PHP Programming Innovation award nominee
December 2018
Number 6
State machines are components that can keep track of the states of a system and evaluate conditions that trigger the transitions between different states of a component.

This package can animate a state machine defined by states, transitions and conditions so it can be used on any applications that can make use of state machines.

Manuel Lemos
Picture of Jorge Castro
  Performance   Level  
Name: Jorge Castro <contact>
Classes: 32 packages by
Country: Chile Chile
Age: 48
All time rank: 12483 in Chile Chile
Week rank: 56 Up1 in Chile Chile Up
Innovation award
Innovation award
Nominee: 14x

Winner: 2x

Example

<?php

use eftec\statemachineone\Job;
use
eftec\statemachineone\StateMachineOne;
use
eftec\statemachineone\Transition;

include
"../vendor/autoload.php";

define("STATE_PICK",1);
define("STATE_CANCEL",2);
define("STATE_TRANSPORT",3);
define("STATE_ABORTTRANSPORT",4);
define("STATE_TODELIVER",5);
define("STATE_HELP",6);
define("STATE_DELIVERED",7);
define("STATE_ABORTED",8);

$smachine=new StateMachineOne(null);
$smachine->setDebug(true);
$smachine->tableJobs="deliver_product";
$smachine->tableJobLogs="deliver_product_log";
$smachine->setDefaultInitState(STATE_PICK);
$smachine->fieldDefault=[
   
'customerpresent'=>null
   
,'addressnotfound'=>null
   
,'signeddeliver'=>null
   
,'abort'=>null
   
,'instock'=>null
   
,'picked'=>null];
$smachine->setdb('mysql','localhost',"root","abc.123","statemachinedb");
$smachine->createDbTable(true); // you don't need to create this table every time.

$smachine->setStopTrigger(function($smo,$job) {echo "Trigger: job is stopping<br>"; return true;});
$smachine->setPauseTrigger(function($smo,$job) {echo "Trigger: job is paused<br>"; return true;},'after');
//$smachine->loadDBActiveJobs();


$smachine->setStates(
        [
STATE_PICK=>'STATE_PICK'
       
,STATE_CANCEL=>'STATE_CANCEL'
       
,STATE_TRANSPORT=>'STATE_TRANSPORT'
       
,STATE_ABORTTRANSPORT=>'STATE_ABORTTRANSPORT'
       
,STATE_TODELIVER=>'STATE_TODELIVER'
       
,STATE_HELP=>'STATE_HELP'
       
,STATE_DELIVERED=>'STATE_DELIVERED'
       
,STATE_ABORTED=>'STATE_ABORTED']);

// if instock = 0 and picked = 1 then change and set instock = 1 , instock = 2
// if _timeout then change and set instock = 1 , instock = 2
$dummy='hello';

function
dummy($job) {
    return
'hello';
}

$smachine->addTransition(STATE_PICK,STATE_CANCEL,'when instock = 0',"stop");
$smachine->addTransition(STATE_PICK,STATE_TRANSPORT,'when picked = 1',"change");
$smachine->addTransition(STATE_TRANSPORT,STATE_TODELIVER,'when addressnotfound = 0',"change");
$smachine->addTransition(STATE_TRANSPORT,STATE_HELP,'when addressnotfound = 1',"change");
//$smachine->addTransition(STATE_HELP,STATE_ABORTED,'when addressnotfound = 9999 timeout 1',"stop"); // we wait 2 seconds, then we give it up

$smachine->addTransition(STATE_HELP,STATE_ABORTED,'when addressnotfound = 9999',"stop"); // we wait 2 seconds, then we give it up
$smachine->addTransition(STATE_HELP,STATE_TODELIVER,'when addressnotfound = 0',"change");
//$smachine->addTransition(STATE_TODELIVER,STATE_DELIVERED
// ,'when signeddeliver = 1 set addressnotfound = 0 , customerpresent = 1 timeout 3600',"stop");

$smachine->addTransition(STATE_TODELIVER,STATE_DELIVERED
   
,'when signeddeliver = 1 set addressnotfound = 0 , customerpresent = 1 timeout 3600',"stop");
$smachine->addEvent('CUSTOMERPRESENT','set addressnotfound = 0 , customerpresent = 1');

$smachine->checkConsistency();

$object=['customerpresent'=>1
   
,'addressnotfound'=>1
   
,'signeddeliver'=>1
   
,'abort'=>-1
   
,'fieldnotstored'=>'hi' // this field is not store or it's part of the state machine
   
,'instock'=>1
   
,'picked'=>1];

$job=$smachine->createJob($object);

$smachine->checkAllJobs();
sleep(2);
$smachine->checkAllJobs();

echo
"<hr>";
$object=['customerpresent'=>-1 // undefined
   
,'addressnotfound'=>-1 // undefined.
   
,'signeddeliver'=>1
   
,'abort'=>-1
   
,'fieldnotstored'=>'hi' // this field is not store
   
,'instock'=>1
   
,'picked'=>1];
$job=$smachine->createJob($object);
$smachine->checkAllJobs();
echo
"calling event CUSTOMERPRESENT<br>";
$smachine->callEvent('CUSTOMERPRESENT');
$smachine->checkAllJobs();



Details

StateMachineOne

It is State Machine library written on PHP aimed to business process. This library has only a simple external dependency, it is a minimalist (yet complete) library with only 3 classes.

Since this library is PHP native, then it could run in Laravel, Symfony and any other frameworks.

Build Status Packagist Total Downloads [Maintenance]() [composer]() [php]() [php]() [CocoaPods]()

What is a state machine?.

A State Machine (also called Automata) is a procedural execution of a job based in states. Every job must have a single state at the same time, such as "INITIATED","PENDING","IN PROCESS" and so on, and the job changes of state (transition) according to some logic or condition. Such conditions could be a field, a time or a custom function.

The target of this library is to ease the process to create a state machine for business.

Notes

  • Job: it's the process to run. A job could have a single state at the same time.
  • State: it's the current condition of the job.
  • Transition: it's the change from one state to another. The transition is conditioned to a set of values, time or a function. Also, every transition could have a timeout. If the timeout is reached then the transition is done, no matter the values or the conditions (even if it has the active state paused). The transition could have 3 outcomes: changeThe transition changes of state and the job is keep active. It is only possible to do the transition if the job has the*active state = active. pauseThe transition changes of state and the job is paused. It is only possible to do the transition if the job has theactive state* = active. continueThe transition changes of state and the job resumes of the pause. It is only possible to do the transition if the job has theactive state* = pause or active stopThe transition changes of state and the job is stopped. It is only possible to do the transition if the job has theactive state* = active or pause. stay* The transition does not change of state but it does the operations defined by the set.
  • Active: Every job has an active state. There are 4: none,stop,active,inactive,pause. It is different from the states. So, for example, a job could have the state: INPROGRESS and the active state: PAUSE. none* = the job doesn't exist. It can't change of state, neither it is loaded (from the database) by default stop* = the job has stopped (finished), it could be a successful, aborted or canceled. It can't change of state neither it is loaded by default. pause* = the job is on hold, it will not change of state (unless it is forced) but it could be continued. active* = the job is running normally, it could change of state. inactive* = the job is scheduled to run at some date. it couldn't change of state unless it is activated (by schedule)
  • Refs: Every job has some related fields. For example, if the job is about an invoice, then refs must be the number of the invoice. The State Machine doesn't use any refs, but it keeps the values for integration with other systems.
  • Fields: Every job has some fields or values, it could trigger a transition.

Example, ChopSuey Chinese Delivery Food.

We need to create a process to deliver Chinese food at home. Is it easy?. Well, let's find it out.

ChopSuey

Fields (ChopSuey's exercise)

Fields are values used for out State Machine. In this example, I am not including other values that it could be useful (such as money, customer name, address and such) because they are not part or used by the state machine.

  • customerpresent =1 if the customer is at home, 0=if not, =null not defined yet
  • addressnotfound =1 if the address is not found by the delivery boy, =0 if found, =null if it's not yet defined.
  • signeddeliver =1 if the customer signed the deliver, =0 if not, =null if its not defined.
  • abort =1 if the deliver must be aborted (for example, an accident), =0 if not.
  • instock =1 if the product is in stock, =0 if it's not, =null if it is not defined.
  • picked =1 if the deliver boy picked and packed the product, =0 if not yet.

States (ChopSuey's exercise)

It must include all the possible situation. The real world is not as easy as: sell and money.

  • STATE_PICK The delivery boy will pick the food if any. For example, what if a customer asks for Pekin's duck (with orange sauce) and the restaurant doesn't have?.

cooker

  • STATE_CANCEL The order is canceled.
  • STATE_TRANSPORT The delivery boy is on route to deliver the food.

cooker

  • STATE_ABORTTRANSPORT Something happened, the delivery must be aborted.
  • STATE_HELP The delivery boy is ready to deliver but he is not able to find the address or maybe there is nobody, so he calls for help.
  • STATE_DELIVERED The food is delivered. Our hero returns to base (Chinese restaurant).

cooker

  • STATE_ABORTED The transaction is aborted, nobody at home or the address is wrong.

Transitions (ChopSuey's exercise)

  • STATE_PICK -> STATE_CANCEL (END) When?. instock=0 (end of the job)
  • STATE_PICK -> STATE_TRANSPORT When?. instock=1 and picked=1
  • STATE_TRANSPORT -> STATE_ABORTTRANSPORT (END) When?. abort=1 (for some reason, our boy abort the transport, is it raining?)
  • STATE_TRANSPORT -> STATE_DELIVERED (END) When?. addressnotfound=0,customerpresent=1 and signeddeliver=1. It is delivered, the customer is present and it signed the deliver (plus a tip, I hope it)
  • STATE_TRANSPORT -> STATE_HELP When?. addressnotfound=1,customerpresent=0 and signeddeliver<>1. Our deliver calls to home and ask for new instructions. Is it Fake Street #1234 the right address?.
  • STATE_HELP -> STATE_ABORTED (END) When?. (15 minutes deadline) or if abort=1. Our deliver called home and yes, the address is fake (it's a shocking surprise)
  • STATE_HELP -> STATE_DELIVERED (END) When?. addressnotfound=0,customerpresent=1 and signeddeliver=1. It is delivered, the customer is present and it signed the deliver (plus a tip, I hope it)

Final Code (ChopSuey's example)

Example/ChopSuey.php

Other examples

Example/BuyMilk.php (buy milk)

Example/Car.php (car parking)

<?php
use eftec\statemachineone\StateMachineOne;

include "vendor/autoload.php";

define("STATE_PICK",1);
define("STATE_CANCEL",2);
define("STATE_TRANSPORT",3);
define("STATE_ABORTTRANSPORT",4);
define("STATE_TODELIVER",5);
define("STATE_HELP",6);
define("STATE_DELIVERED",7);
define("STATE_ABORTED",8);

$smachine=new StateMachineOne();
$smachine->setDebug(true);
$smachine->tableJobs="chopsuey_jobs";
$smachine->tableJobLogs="chopsuey_logs";

$smachine->setDefaultInitState(STATE_PICK);
$smachine->setAutoGarbage(false); // we don't want to delete automatically a stopped job.
$smachine->setStates([STATE_PICK=>'Pick order'
	,STATE_CANCEL=>'Cancel order'
	,STATE_TRANSPORT=>'Transport order'
	,STATE_ABORTTRANSPORT=>'Abort the delivery'
	,STATE_TODELIVER=>'Pending to deliver'
	,STATE_HELP=>'Request assistance'
	,STATE_DELIVERED=>'Delivered'
	,STATE_ABORTED=>'Aborted']);

$smachine->fieldDefault=[
	'customerpresent'=>-1
	,'addressnotfound'=>-1
	,'signeddeliver'=>-1
	,'abort'=>-1
	,'instock'=>-1
	,'picked'=>-1
	,'message'=>''];
$smachine->setDB('mysql','localhost',"root","abc.123","statemachinedb");
$smachine->createDbTable(false); // you don't need to create this table every time.

$smachine->loadDBAllJob(); // we load all jobs, including finished ones.

// business rules
$smachine->addTransition(STATE_PICK,STATE_PICK
	,'when instock = 0 set message="without stock"','stay'); // it stays in the same state
$smachine->addTransition(STATE_PICK,STATE_CANCEL
	,'when instock = 0 set abort = 1','stop'); // ends the process
$smachine->addTransition(STATE_PICK,STATE_TRANSPORT
	,'when instock = 1','change'); // changes transition
$smachine->addTransition(STATE_TRANSPORT,STATE_ABORTTRANSPORT
	,'when abort = 1','stop'); // ends the process
$smachine->addTransition(STATE_TRANSPORT,STATE_DELIVERED
	,'when addressnotfound = 0 and customerpresent = 1 and signeddeliver = 1 timeout 3600','stop'); // 1 hour max.
$smachine->addTransition(STATE_TRANSPORT,STATE_HELP
	,'when addressnotfound = 1 or customerpresent = 0 timeout 3600','change'); // 1 hour max
$smachine->addTransition(STATE_HELP,STATE_ABORTED
	,'when wait timeout 900','change'); // it waits 15 minutes max.
$smachine->addTransition(STATE_HELP,STATE_DELIVERED
	,'when addressnotfound = 0 and customerpresent = 1 and signeddeliver = 1','change');


$msg=$smachine->fetchUI();
$smachine->checkAllJobs();

$smachine->viewUI(null,$msg); // null means it takes the current job

Transition language

Let's say the next transition

$smachine->addTransition(STATE_PICK,STATE_CANCEL
	,'when instock = 0 set abort = 1','stop');

The transition is written as follow: * initial state * end state * Transition language outcome, it could bechange(default value),stop,pause,continueandstay*

changemeans the state will change frominitial statetoend state* if it meets the condition (or timeout).  It will only change if the state is active.  
stop* means the state will change and the job will stop (end of the job)  
pause* it means the state will change and the job will pause.  A job paused can't change of state, even if it meets the condition.  
continue* it means the state will change and the job will continue from pause.
stay* it means the state will not change (but it executes any other instruction).  

The transition language is written with the next syntax.

> _when_ var1 = var2 and var3 = var4 or var4 = var5 > _set_ var1 = var2 , var3 = var4 > _timeout_ var1 > _fulltimeout_ var2 there are two operations we could dowhenand/orset*

Transition when

The transition happens when this condition meets. For example: > when field=0 // it happens when the field is zero. > when $var='hi' // it happens when the global variable is 'hi' > when fn()=44 // the transition is triggered when the function fn() returns 44 > when always // its always true. It is the same than "when 1=1". The transition is always executed

It compares a constant. The binary operator for comparison are * = Equals &lt;&gt;* Not equals &lt; &lt;=* Less and less than &gt; &gt;=* Great and great than contain* If a text contains other. > when field contain 'text'

Values of the field could be as the next ones: field* = it is a field of the job. > when field = field2 // when field (of the job) is equals to field2 _idjob* = it is the number of the current job. It is calculated every time the job is evaluated > set id=_idjob $var* = it is a global variable (php) 777* = it is a numeric constant > when field = 777 // when field is equals to 777 "AAA",'aaa'* = it is a literal > when field = 'hello' // when field is equals to the textr hello function()* = it is a global function. Every function must have the parameter $job. > when field = somefunc() // function somefunc(Job $job) {...} null()* it is the null value > when field = null() true()* it is the true value (true) > when field = true() // when field is equals to true false()* it is the false value (false) > when field = true() // when field is equals to false on()* it is the on value (1) off()* it is the off value (0) undef()* it is the undefined value (-1) flip()* indicates that the value will be flipped (1=>0 and 0=>1). Example (x=1) x = flip(), now (x=0). If the value is not zero, then it's flipped to zero. > set field=flip() // it is only valid for set. now()* it defines the current timestamp (in seconds) interval()* it returns the current interval between now and the last state. fullinterval()* it returns the current interval between now and the start of the job.

For example > when field2 = 2 and field3 > someFunction() and field4=$var > > Where somefunction must be defined as someFunction(Job $job) {}

Transition set

> set field = 0 , field2 = 3

It sets a field of the job.

  • The first value of the operation can't be a constant. > set 0 = 20 // is invalid
  • The first value could be a function. > set myfunc() = 20 > > Where the function (global) must be defined as myfunc(Job $job,$input) {}
  • The first value could be a field or a (global) variable > set field=20 > set $variable=20

> set field = 0

It sets the field to the value 0

> set field + 1

It increases the value of field by 1 (field=field+1)

> set field - 1

It decreases the value of field by 1 (field=field-1)

Transition timeout (in seconds)

It sets the timeout between the time of current state and the current time. If a timeout happens, then the transition is executed.

> timeout 3600 // 1 hour timeout > timeout field // timeout by field, it is calculated each time.

Transition fulltimeout (in seconds)

It sets the timeout between the time of initial state and the current time. If a timeout happens, then the transition is executed.

> fulltimeout 3600 // 1 hour timeout > fulltimeout field // timeout by field, the field is evaluated each time.

GUI

This library has a build-in GUI for testing.

GUID

Classes

StateMachineOne It is the main class. Job It is the model class for the job Transition It is the model class for the transitions.

License

Dual license (LGPL 3.0 and Commercial). See LICENSE file.

Version

  • 2.4 2019-12-26 * Service Object now works on events. * Updated library eftec/PdoOne to 1.15
  • 2.3 2019-12-26 * Method Createdbtable() now sets a valid default value * Some cleanups. * Bootstrap updated to 4.4.1. Also it is using https instead of http
  • 2.2 2019-10-22 * Updated eftec/MiniLang (dependency) from 2.9 => 2.12 * Updated eftec/pdoone (depe ndency) from 1.11 => 1.12 * Added dependency/documentstoreone (dependency) to 1.11 (it allows to use the filesystem as database) * New methods setDocOne(), getDocOne() * Now the library allows to use pdo (mysql database) or a file system (documentOne) for the persistence. * Fixed a problem with the UI (it only executed the last job) * Added changes to the UI. Now, it is possible to view and change the current job. * Fixed a problem with the creation of table. Now the column TEXT_JOB is always created.
  • 2.1 2019-08-28 * Updated eftec/minilang to 2.9. * It allows to store arrays in each field * If the job's field is an object or array, then it is store in a MEDIUMTEXT FIELD (serialized) * method Flags::flagexist() * method StateMachineOne::removetransition()
  • 2.0 2019-08-24 * Changed the flags. The definition of push is flipped. After push('msg','id'..) now push('id','msg'..) * Added method to set the time.
  • 1.12 2019-08-16 * Updated MiniLang * Added method viewJson() * Now event doesn't crash if the job is null. * CreateColTable() (private method) is removed. * Flag() now has expiration (optional)
  • 1.11 2019-08-04 Some fixes.
  • 1.10 2019-08-04 * Updated to "eftec/minilang": "^2.7" * Solved a bug in callEvent() does not fail if there is not a job. * Added the method cacheMachine() to cache the results. * Code Formatted to PSR-2
  • 1.9 2019-08-03 * Some fixes. Now the UI doesn't show events that "stay" in the same state. * Now it uses eftec/minilang 2.6 that permits the use of "else" * The UI is more reduced. * Method createColsTable() added.
  • 1.8 2019-07-29 Some cleanups and methods setPdoOne() and getPdoOne();
  • 1.7 2019-06-16 Lots of changes.
  • 1.6 2018-12-26 Now MiniLang is a separate dependency.
  • 1.5 2018-12-23 Xmas update (btw porca miseria). * Now the language is parsed differently. The space is not mandatory anymore. * "when timeout" is not deprecated. Now it is called as "when always"
  • 1.4 2018-12-12 * Some fixes.
  • 1.3 2018-12-11 * Added addEvent() and callEvent() * Added timeout and fulltimeout to the transition language * Now transitions doesn't require the timeout. * idRef are not longer used.
  • 1.2 2018-12-09 Updated dependency
  • 1.1 2018-12-09 Some corrections.
  • 1.0 2018-12-08 First (non beta) version.

What is missing

  • ~~events and timeout~~
  • Most unit test, ~~now it is only the barebone.~~ the unit test is real but it's still basic.
  • Increase the log features.

  Files folder image Files (45)  
File Role Description
Files folder imageDocs (5 files)
Files folder imageexample (12 files, 1 directory)
Files folder imagelib (9 files)
Files folder imagetests (4 files, 1 directory)
Accessible without login Plain text file .travis.yml Data Auxiliary data
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file Job.md Data Auxiliary data
Accessible without login Plain text file Job.md Data Auxiliary data
Accessible without login Plain text file LICENSE Lic. License text
Accessible without login Plain text file phpunit.xml Data Auxiliary data
Accessible without login Plain text file README.md Doc. Documentation
Accessible without login Plain text file StateMachineOne.md Data Auxiliary data
Accessible without login Plain text file StateMachineOne.md Data Auxiliary data
Accessible without login Plain text file Transition.md Data Auxiliary data
Accessible without login Plain text file Transition.md Data Auxiliary data

  Files folder image Files (45)  /  Docs  
File Role Description
  Accessible without login Image file ChopSuey.jpg Data Auxiliary data
  Accessible without login Image file cooker.jpg Data Auxiliary data
  Accessible without login Image file deliveryboy.jpg Data Auxiliary data
  Accessible without login Image file food.jpg Data Auxiliary data
  Accessible without login Image file milk1.jpg Data Auxiliary data

  Files folder image Files (45)  /  example  
File Role Description
Files folder imageexampledb (1 directory)
  Accessible without login Plain text file BuyMilk.php Example Example script
  Accessible without login Plain text file Car.php Example Example script
  Accessible without login Plain text file Car.php Example Example script
  Accessible without login Plain text file ChopSuey.php Example Example script
  Accessible without login Plain text file ChopSueyDoc.php Example Example script
  Accessible without login Plain text file ChopSueyDoc.php Example Example script
  Accessible without login Plain text file example1.php Aux. Auxiliary script
  Accessible without login Plain text file example2.php Example Example script
  Accessible without login Plain text file example2withoutdatabase.php Example Example script
  Accessible without login Plain text file example2withoutdatabase.php Example Example script
  Accessible without login Plain text file hiring.php Example Example script
  Accessible without login Plain text file hiring.php Example Example script

  Files folder image Files (45)  /  example  /  exampledb  
File Role Description
Files folder imagechopsuey (2 files)

  Files folder image Files (45)  /  example  /  exampledb  /  chopsuey  
File Role Description
  Accessible without login Plain text file genseq_seq_chopsuey_jobs.dson.seq Data Auxiliary data
  Accessible without login Plain text file genseq_seq_chopsuey_jobs.dson.seq Data Auxiliary data

  Files folder image Files (45)  /  lib  
File Role Description
  Plain text file Flags.php Class Class source
  Plain text file Flags.php Class Class source
  Plain text file Job.php Class Class source
  Plain text file Pending.php Class Class source
  Plain text file Pending.php Class Class source
  Plain text file StateMachineOne.php Class Class source
  Plain text file StateSerializable.php Class Class source
  Plain text file StateSerializable.php Class Class source
  Plain text file Transition.php Class Class source

  Files folder image Files (45)  /  tests  
File Role Description
Files folder imagetmpdoc (2 files)
  Plain text file AbstractStateMachineOneTestCase.php Class Class source
  Plain text file AbstractStateMachineOneTestCase.php Class Class source
  Plain text file StateMachineTest.php Class Class source
  Plain text file StateMachineTest.php Class Class source

  Files folder image Files (45)  /  tests  /  tmpdoc  
File Role Description
  Accessible without login Plain text file dummy.txt Doc. Documentation
  Accessible without login Plain text file dummy.txt Doc. Documentation

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads Download Rankings  
 100%
Total:141
This week:0
All time:9,197
This week:488Up