Skip to content

Commit

Permalink
added xml parser and builder
Browse files Browse the repository at this point in the history
  • Loading branch information
shrink0r committed Oct 4, 2014
1 parent 76b0cac commit fcbf923
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/Builder/StateMachineBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function addTransitions(array $events)
}

foreach ($transitions as $transition) {
$this->addTransition((string)$event_name, $transition);
$this->addTransition($event_name, $transition);
}
}

Expand Down
41 changes: 41 additions & 0 deletions src/Builder/XmlStateMachineBuilder.php
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();
}
}
8 changes: 8 additions & 0 deletions src/Parser/IParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Workflux\Parser;

interface IParser
{

}
131 changes: 131 additions & 0 deletions src/Parser/Xml/StateMachineDefinitionParser.php
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;
}
}
20 changes: 20 additions & 0 deletions tests/Builder/XmlStateMachineBuilderTest.php
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();
}
}
108 changes: 108 additions & 0 deletions tests/Parser/Xml/Fixture/state_machine.php
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' => []
]
]
];
52 changes: 52 additions & 0 deletions tests/Parser/Xml/Fixture/state_machine.xml
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>
20 changes: 20 additions & 0 deletions tests/Parser/Xml/StateMachineDefinitionParserTest.php
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);
}
}

0 comments on commit fcbf923

Please sign in to comment.