-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
381 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace Workflux\Builder; | ||
|
||
use Workflux\Transition\Transition; | ||
use Workflux\State\State; | ||
use Workflux\Parser\Xml\StateMachineDefinitionParser; | ||
|
||
class XmlStateMachineBuilder extends StateMachineBuilder | ||
{ | ||
public function build() | ||
{ | ||
$state_machine_definition_file = $this->getOption('state_machine_definition'); | ||
|
||
$parser = new StateMachineDefinitionParser(); | ||
$state_machine_definition = $parser->parse($state_machine_definition_file); | ||
|
||
$this->setStateMachineName($state_machine_definition['name']); | ||
foreach ($state_machine_definition['states'] as $state_name => $state_definition) { | ||
$state = new State($state_name, $state_definition['type']); | ||
$this->addState($state); | ||
|
||
foreach ($state_definition['events'] as $event_name => $event_definition) { | ||
foreach ($event_definition['transitions'] as $transition_definition) { | ||
$target = $transition_definition['outgoing_state_name']; | ||
$guard_definition = $transition_definition['guard']; | ||
|
||
$guard = null; | ||
if ($guard_definition) { | ||
$guard = new $guard_definition['class']($guard_definition['options']); | ||
} | ||
|
||
$transition = new Transition($state_name, $target, $guard); | ||
$this->addTransition($event_name, $transition); | ||
} | ||
} | ||
} | ||
|
||
return parent::build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Workflux\Parser; | ||
|
||
interface IParser | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<?php | ||
|
||
namespace Workflux\Parser\Xml; | ||
|
||
use Workflux\Parser\IParser; | ||
use Workflux\Error\Error; | ||
use Workflux\State\IState; | ||
use DOMDocument; | ||
use DOMXpath; | ||
use DOMElement; | ||
|
||
class StateMachineDefinitionParser implements IParser | ||
{ | ||
protected $xpath; | ||
|
||
public function parse($state_machine_definition_file) | ||
{ | ||
if (!is_readable($state_machine_definition_file)) { | ||
throw new Error( | ||
sprintf("Unable to read fsm definition file at location: %s", $state_machine_definition_file) | ||
); | ||
} | ||
|
||
$document = new DOMDocument(); | ||
$document->load($state_machine_definition_file); | ||
// @todo write xsd and use it here to validate stuff. | ||
$this->xpath = new DOMXpath($document); | ||
|
||
return $this->parseStateMachineNode( | ||
$this->xpath->query('//state_machine')->item(0) | ||
); | ||
} | ||
|
||
protected function parseStateMachineNode(DOMElement $state_machine_node) | ||
{ | ||
$state_machine_name = $state_machine_node->getAttribute('name'); | ||
|
||
$state_node_expressions = [ 'initial', 'state', 'final' ]; | ||
foreach ($state_node_expressions as $state_node_expression) { | ||
foreach ($this->xpath->query($state_node_expression, $state_machine_node) as $state_node) { | ||
$state_node_data = $this->parseStateNode($state_node); | ||
$state_name = $state_node_data['name']; | ||
$state_nodes_data[$state_name] = $state_node_data; | ||
} | ||
} | ||
|
||
return [ 'name' => $state_machine_name, 'states' => $state_nodes_data ]; | ||
} | ||
|
||
protected function parseStateNode(DOMElement $state_node) | ||
{ | ||
$state_name = $state_node->getAttribute('name'); | ||
$events = []; | ||
foreach($this->xpath->query('event', $state_node) as $event_node) { | ||
$event_data = $this->parseEventNode($event_node); | ||
$event_name = $event_data['name']; | ||
$events[$event_name] = $event_data; | ||
} | ||
|
||
switch ($state_node->nodeName) { | ||
case 'initial': | ||
$state_type = IState::TYPE_INITIAL; | ||
break; | ||
case 'final': | ||
$state_type = IState::TYPE_FINAL; | ||
break; | ||
default: | ||
$state_type = IState::TYPE_ACTIVE; | ||
} | ||
|
||
return [ 'name' => $state_name, 'events' => $events, 'type' => $state_type ]; | ||
} | ||
|
||
protected function parseEventNode(DOMElement $event_node) | ||
{ | ||
$event_name = $event_node->getAttribute('name'); | ||
$transitions = []; | ||
foreach($this->xpath->query('transition', $event_node) as $transition_node) { | ||
$transitions[] = $this->parseTransitionNode($transition_node); | ||
} | ||
|
||
return [ 'name' => $event_name, 'transitions' => $transitions ]; | ||
} | ||
|
||
protected function parseTransitionNode(DOMElement $transition_node) | ||
{ | ||
$outgoing_state_name = $transition_node->getAttribute('target'); | ||
$guard_node = $this->xpath->query('guard', $transition_node)->item(0); | ||
|
||
if (!$guard_node) { | ||
$guard_data = null; | ||
} else { | ||
$guard_data = $this->parseGuardNode($guard_node); | ||
} | ||
|
||
return [ 'outgoing_state_name' => $outgoing_state_name, 'guard' => $guard_data ]; | ||
} | ||
|
||
protected function parseGuardNode(DOMElement $guard_node) | ||
{ | ||
$guard_class = $guard_node->getAttribute('class'); | ||
$guard_options = []; | ||
foreach ($this->xpath->query('option', $guard_node) as $option_node) { | ||
$option_name = $option_node->getAttribute('name'); | ||
$guard_options[$option_name] = $this->literalize($option_node->nodeValue); | ||
} | ||
|
||
return [ 'class' => $guard_class, 'options' => $guard_options ]; | ||
} | ||
|
||
protected function literalize($value) | ||
{ | ||
if (!is_string($value)) { | ||
return $value; | ||
} | ||
|
||
$value = trim($value); | ||
if ($value == '') { | ||
return null; | ||
} | ||
|
||
$lc_value = strtolower($value); | ||
if ($lc_value === 'on' || $lc_value === 'yes' || $lc_value === 'true') { | ||
return true; | ||
} elseif ($lc_value === 'off' || $lc_value === 'no' || $lc_value === 'false') { | ||
return false; | ||
} | ||
|
||
return $value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
namespace Workflux\Tests\Builder; | ||
|
||
use Workflux\Tests\BaseTestCase; | ||
use Workflux\Builder\XmlStateMachineBuilder; | ||
|
||
class XmlStateMachineBuilderTest extends BaseTestCase | ||
{ | ||
public function testBuild() | ||
{ | ||
$state_machine_definition_file = dirname(__DIR__) . '/Parser/Xml/Fixture/state_machine.xml'; | ||
|
||
$builder = new XmlStateMachineBuilder( | ||
[ 'state_machine_definition' => $state_machine_definition_file ] | ||
); | ||
|
||
$builder->build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
return [ | ||
'name' => 'video_transcoding', | ||
'states' => [ | ||
'new' => [ | ||
'name' => 'new', | ||
'type' => 'initial', | ||
'events' => [ | ||
'promote' => [ | ||
'name' => 'promote', | ||
'transitions' => [ | ||
[ | ||
'outgoing_state_name' => 'transcoding', | ||
'guard' => [ | ||
'class' => 'Workflux\Guard\ExpressionGuard', | ||
'options' => [ | ||
'expression' => 'params.transcoding_required' | ||
] | ||
] | ||
], | ||
[ | ||
'outgoing_state_name' => 'ready', | ||
'guard' => [ | ||
'class' => 'Workflux\Guard\ExpressionGuard', | ||
'options' => [ | ||
'expression' => 'not params.transcoding_required' | ||
] | ||
] | ||
] | ||
] | ||
] | ||
] | ||
], | ||
'transcoding' => [ | ||
'name' => 'transcoding', | ||
'type' => 'active', | ||
'events' => [ | ||
'promote' => [ | ||
'name' => 'promote', | ||
'transitions' => [ | ||
[ | ||
'outgoing_state_name' => 'ready', | ||
'guard' => null | ||
] | ||
] | ||
], | ||
'demote' => [ | ||
'name' => 'demote', | ||
'transitions' => [ | ||
[ | ||
'outgoing_state_name' => 'error', | ||
'guard' => [ | ||
'class' => 'Workflux\Guard\ExpressionGuard', | ||
'options' => [ | ||
'expression' => 'not params.retry_limit_reached' | ||
] | ||
] | ||
], | ||
[ | ||
'outgoing_state_name' => 'rejected', | ||
'guard' => [ | ||
'class' => 'Workflux\Guard\ExpressionGuard', | ||
'options' => [ | ||
'expression' => 'params.retry_limit_reached' | ||
] | ||
] | ||
] | ||
] | ||
] | ||
] | ||
], | ||
'error' => [ | ||
'name' => 'error', | ||
'type' => 'active', | ||
'events' => [ | ||
'promote' => [ | ||
'name' => 'promote', | ||
'transitions' => [ | ||
[ | ||
'outgoing_state_name' => 'transcoding', | ||
'guard' => null | ||
] | ||
] | ||
], | ||
'demote' => [ | ||
'name' => 'demote', | ||
'transitions' => [ | ||
[ | ||
'outgoing_state_name' => 'rejected', | ||
'guard' => null | ||
] | ||
] | ||
] | ||
] | ||
], | ||
'rejected' => [ | ||
'name' => 'rejected', | ||
'type' => 'final', | ||
'events' => [] | ||
], | ||
'ready' => [ | ||
'name' => 'ready', | ||
'type' => 'final', | ||
'events' => [] | ||
] | ||
] | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
|
||
<state_machines> | ||
<state_machine name="video_transcoding"> | ||
|
||
<initial name="new"> | ||
<event name="promote"> | ||
<transition target="transcoding"> | ||
<guard class="Workflux\Guard\ExpressionGuard"> | ||
<option name="expression">params.transcoding_required</option> | ||
</guard> | ||
</transition> | ||
<transition target="ready"> | ||
<guard class="Workflux\Guard\ExpressionGuard"> | ||
<option name="expression">not params.transcoding_required</option> | ||
</guard> | ||
</transition> | ||
</event> | ||
</initial> | ||
|
||
<state name="transcoding"> | ||
<event name="promote"> | ||
<transition target="ready" /> | ||
</event> | ||
<event name="demote"> | ||
<transition target="error"> | ||
<guard class="Workflux\Guard\ExpressionGuard"> | ||
<option name="expression">not params.retry_limit_reached</option> | ||
</guard> | ||
</transition> | ||
<transition target="rejected"> | ||
<guard class="Workflux\Guard\ExpressionGuard"> | ||
<option name="expression">params.retry_limit_reached</option> | ||
</guard> | ||
</transition> | ||
</event> | ||
</state> | ||
|
||
<state name="error"> | ||
<event name="promote"> | ||
<transition target="transcoding" /> | ||
</event> | ||
<event name="demote"> | ||
<transition target="rejected" /> | ||
</event> | ||
</state> | ||
|
||
<final name="rejected" /> | ||
<final name="ready" /> | ||
|
||
</state_machine> | ||
</state_machines> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
namespace Workflux\Tests\Parser\Xml; | ||
|
||
use Workflux\Tests\BaseTestCase; | ||
use Workflux\Parser\Xml\StateMachineDefinitionParser; | ||
|
||
class StateMachineDefinitionParserTest extends BaseTestCase | ||
{ | ||
public function testParse() | ||
{ | ||
$state_machine_definition_file = dirname(__FILE__) . '/Fixture/state_machine.xml'; | ||
$parser = new StateMachineDefinitionParser(); | ||
|
||
$expected = include dirname(__FILE__) . '/Fixture/state_machine.php'; | ||
$parsed_definition = $parser->parse($state_machine_definition_file); | ||
|
||
$this->assertEquals($expected, $parsed_definition); | ||
} | ||
} |