1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
28:
29: namespace Mothership\StateMachine;
30:
31: use Mothership\Exception\StateMachine\StateMachineException;
32: use Mothership\Exception\StateMachine\WorkflowException;
33: use Symfony\Component\Yaml\Yaml;
34: use \Symfony\Component\Console\Output\OutputInterface;
35: use Mothership\StateMachine\Transition;
36:
37: abstract class StateMachineAbstract implements StateMachineInterface
38: {
39: protected $workflow_file;
40: protected $workflow_array;
41: protected $workflow;
42: protected $output;
43:
44: public function __construct($file = null, OutputInterface $output)
45: {
46: $this->output = $output;
47: $this->workflow_file = $file;
48: if (!file_exists($this->workflow_file) || is_null($file)) {
49: throw new StateMachineException("File " . $this->workflow_file . " doesn't exist or null, you
50: must provide an existing workflow YAML file",
51: 100, null, $this->output);
52: }
53: if ($output === null) {
54: throw new StateMachineException("No Output is defined for Mothership State Machine", 99, null, $this->output);
55: }
56:
57: try {
58: $this->workflow_array = $this->parseYAML();
59: if ($this->workflow_array === false || $this->workflow_array === null) {
60: throw new StateMachineException("Error parsing " . $this->workflow_file . " file", 98, null, $this->output);
61: }
62: } catch (Symfony\Component\Yaml\Exception\ParseException $ex) {
63: throw new StateMachineException("Error parsing " . $this->workflow_file . " file", 98, $ex, $this->output);
64: }
65:
66: $this->initWorkflow();
67: }
68:
69: 70: 71: 72: 73: 74:
75: protected function parseYAML()
76: {
77: try {
78: $yaml = Yaml::parse(file_get_contents($this->workflow_file));
79: $yaml_fixed = [];
80: $yaml_fixed['class'] = $yaml['class'];
81: foreach ($yaml['states'] as $key => $value) {
82: if ($value['type'] != 'initial') {
83: $state = ['name' => $key,
84: 'type' => $value['type'],
85: 'transitions_from' => $value['transitions_from'],
86: 'transitions_to' => $value['transitions_to']];
87: $yaml_fixed['states'][] = $state;
88: } else {
89: $state = ['name' => $key,
90: 'type' => $value['type']];
91: $yaml_fixed['states'][] = $state;
92: }
93: }
94: return $yaml_fixed;
95: } catch (Symfony\Component\Yaml\Exception\ParseException $ex) {
96: throw $ex;
97: }
98: }
99:
100: 101: 102:
103: protected function initWorkflow()
104: {
105: try {
106: $class_name = $this->workflow_array['class']['name'];
107: $this->workflow = new $class_name($this->output, $this->workflow_array);
108: } catch (WorkflowException $ex) {
109: throw new StateMachineException("Workflow with some problems", 90, $ex, $this->output);
110: }
111:
112: }
113:
114: 115: 116: 117:
118: public function renderGraph($outputPath = './workflow.png', $exit = true)
119: {
120:
121: 122: 123: 124: 125:
126: $template
127: = "
128: digraph finite_state_machine {
129: rankdir=LR;
130: size=\"%d\"
131:
132: node [shape = doublecircle]; S;
133: node [shape = circle];
134:
135: %s
136: }
137: ";
138:
139: $pattern = " %s -> %s [ label = \"%s\" ];";
140:
141: $_transitions = array();
142: foreach ($this->workflow_array['states'] as $state) {
143: if (array_key_exists("transitions_from", $state)) {
144: $transitions_from = $state['transitions_from'];
145: foreach ($transitions_from as $from) {
146: if (is_array($from)) {
147: $_transitions[] = sprintf($pattern, $from['status'], $state['name'], "<< IF "
148: . $from['result'] . " >>" . $state['name']);
149: } else {
150: $_transitions[] = sprintf($pattern, $from, $state['name'], $state['name']);
151: }
152: }
153: }
154: }
155: file_put_contents('/tmp/sm.gv', sprintf($template, count($_transitions) * 2, implode("\n", $_transitions)));
156: shell_exec('dot -Tpng /tmp/sm.gv -o ' . $outputPath);
157:
158: if ($exit) {
159: exit;
160: }
161: }
162:
163: 164: 165:
166: public function run()
167: {
168: try {
169: return $this->workflow->run();
170: } catch (WorkflowException $ex) {
171: throw new StateMachineException("Error running State Machine", 100, $ex, $this->output);
172: }
173: }
174:
175: }
176: