1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the phpflo\phpflo-fbp package. |
4
|
|
|
* |
5
|
|
|
* (c) Marc Aschmann <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace PhpFlo\Fbp; |
12
|
|
|
|
13
|
|
|
use PhpFlo\Common\FbpDefinitionsInterface; |
14
|
|
|
use PhpFlo\Exception\DumperException; |
15
|
|
|
use Symfony\Component\Yaml\Yaml; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Class FbpDumper |
19
|
|
|
* |
20
|
|
|
* @package PhpFlo\Fbp |
21
|
|
|
* @author Marc Aschmann <[email protected]> |
22
|
|
|
*/ |
23
|
|
|
final class FbpDumper implements FbpDefinitionsInterface |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var array |
27
|
|
|
*/ |
28
|
|
|
private static $processes; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @param array $definition |
32
|
|
|
* @return string json |
33
|
|
|
*/ |
34
|
2 |
|
public static function toJson(array $definition) |
35
|
|
|
{ |
36
|
2 |
|
return json_encode($definition, JSON_PRETTY_PRINT); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @param array $definition |
41
|
|
|
* @param int $inline level until inlining starts |
42
|
|
|
* @return string yaml |
43
|
|
|
*/ |
44
|
2 |
|
public static function toYaml(array $definition, $inline = 3) |
45
|
|
|
{ |
46
|
2 |
|
return Yaml::dump($definition, $inline); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param array $definition |
51
|
|
|
* @return string |
52
|
|
|
*/ |
53
|
1 |
|
public static function toFbp(array $definition) |
54
|
|
|
{ |
55
|
1 |
|
return self::createFbp($definition); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @param array $definition |
60
|
|
|
* @return string |
61
|
|
|
*/ |
62
|
2 |
|
private static function createFbp(array $definition) |
63
|
|
|
{ |
64
|
2 |
|
$fbp = []; |
65
|
|
|
|
66
|
|
|
try { |
67
|
|
|
|
68
|
2 |
|
if (self::hasElement(self::PROPERTIES_LABEL, $definition)) { |
69
|
2 |
|
foreach($definition[self::PROPERTIES_LABEL] as $name => $element) { |
70
|
2 |
|
if (!empty($element)) { |
71
|
1 |
|
$fbp[] = '# ' . (string)$element; |
72
|
1 |
|
} |
73
|
2 |
|
} |
74
|
2 |
|
} |
75
|
|
|
|
76
|
|
|
// first check for process definitions |
77
|
2 |
|
if (self::hasElement(self::PROCESSES_LABEL, $definition)) { |
78
|
2 |
|
self::$processes = $definition[self::PROCESSES_LABEL]; |
79
|
2 |
|
} |
80
|
|
|
|
81
|
|
|
// handle initializer |
82
|
2 |
|
if (!empty($definition[self::INITIALIZERS_LABEL])) { |
83
|
1 |
|
foreach ($definition[self::INITIALIZERS_LABEL] as $initializer) { |
84
|
1 |
|
if (empty($initializer[self::DATA_LABEL])) { |
85
|
|
|
self::throwDumperException('no_definition', self::DATA_LABEL); |
86
|
|
|
} |
87
|
1 |
|
if (empty($initializer[self::TARGET_LABEL])) { |
88
|
|
|
self::throwDumperException('no_definition', self::TARGET_LABEL); |
89
|
|
|
} |
90
|
1 |
|
array_push( |
91
|
1 |
|
$fbp, |
92
|
1 |
|
self::connectPorts( |
93
|
1 |
|
$initializer[self::DATA_LABEL], |
94
|
1 |
|
self::examineProcess(self::TARGET_LABEL, $initializer[self::TARGET_LABEL]) |
95
|
1 |
|
) |
96
|
1 |
|
); |
97
|
1 |
|
} |
98
|
1 |
|
} |
99
|
|
|
|
100
|
2 |
|
foreach ($definition[self::CONNECTIONS_LABEL] as $connection) { |
101
|
2 |
|
array_push($fbp, self::examineConnectionTouple($connection)); |
102
|
2 |
|
} |
103
|
|
|
|
104
|
2 |
|
return implode(self::FILE_LINEFEED, $fbp); |
105
|
|
|
} catch (\Exception $e) { |
106
|
|
|
throw new DumperException( |
107
|
|
|
"Unexpected dumper error \"{$e->getMessage()}\" in {$e->getFile()} on Line {$e->getLine()}" |
108
|
|
|
); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Look for all needed fields and build a port -> port connection. |
114
|
|
|
* |
115
|
|
|
* @param array $connectionTouple |
116
|
|
|
* @return string |
117
|
|
|
*/ |
118
|
1 |
|
private static function examineConnectionTouple(array $connectionTouple) |
119
|
|
|
{ |
120
|
1 |
|
self::hasElement(self::SOURCE_LABEL, $connectionTouple); |
121
|
1 |
|
self::hasElement(self::TARGET_LABEL, $connectionTouple); |
122
|
|
|
|
123
|
1 |
|
return self::connectPorts( |
124
|
1 |
|
self::examineProcess(self::SOURCE_LABEL, $connectionTouple[self::SOURCE_LABEL]), |
125
|
1 |
|
self::examineProcess(self::TARGET_LABEL, $connectionTouple[self::TARGET_LABEL]) |
126
|
1 |
|
); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param string $type |
131
|
|
|
* @param array $processPart |
132
|
|
|
* @throws DumperException |
133
|
|
|
* @return string |
134
|
|
|
*/ |
135
|
2 |
|
private static function examineProcess($type, array $processPart) |
136
|
|
|
{ |
137
|
2 |
|
self::hasElement(self::PROCESS_LABEL, $processPart); |
138
|
2 |
|
self::hasElement(self::PORT_LABEL, $processPart); |
139
|
|
|
|
140
|
2 |
|
$inport = ''; |
141
|
2 |
|
$outport = ''; |
142
|
2 |
|
$process = $processPart[self::PROCESS_LABEL]; |
143
|
2 |
|
$port = $processPart[self::PORT_LABEL]; |
144
|
|
|
|
145
|
2 |
|
if (self::hasElement($process, self::$processes, false)) { |
146
|
2 |
|
$meta = "(" . self::$processes[$process][self::COMPONENT_LABEL] . ")"; |
147
|
2 |
|
} else { |
148
|
|
|
self::throwDumperException('process', $process); |
149
|
|
|
} |
150
|
|
|
|
151
|
2 |
|
if (self::SOURCE_LABEL == $type) { |
152
|
2 |
|
$outport = " {$port}"; |
153
|
2 |
|
} else { |
154
|
2 |
|
$inport = "{$port} "; |
155
|
|
|
} |
156
|
|
|
|
157
|
2 |
|
return "{$inport}{$process}{$meta}{$outport}"; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param string $needle |
162
|
|
|
* @param array $haystack |
163
|
|
|
* @param bool $triggerException |
164
|
|
|
* @return bool |
165
|
|
|
*/ |
166
|
2 |
|
private static function hasElement($needle, array $haystack, $triggerException = true) |
167
|
|
|
{ |
168
|
2 |
|
if (empty($haystack[$needle])) { |
169
|
|
|
if ($triggerException) { |
170
|
|
|
self::throwDumperException('elmeent', $needle); |
171
|
|
|
} else { |
172
|
|
|
return false; |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
2 |
|
return true; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* @param string $sourcePort |
181
|
|
|
* @param string $targetPort |
182
|
|
|
* @return string |
183
|
|
|
*/ |
184
|
1 |
|
private static function connectPorts($sourcePort, $targetPort) |
185
|
|
|
{ |
186
|
1 |
|
return implode( |
187
|
1 |
|
" " . self::SOURCE_TARGET_SEPARATOR . " ", |
188
|
|
|
[ |
189
|
1 |
|
$sourcePort, |
190
|
|
|
$targetPort |
191
|
1 |
|
] |
192
|
1 |
|
); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
private static function throwDumperException($type, $value) |
196
|
|
|
{ |
197
|
|
|
switch ($type) { |
198
|
|
|
case 'element': |
199
|
|
|
throw new DumperException("Element has no {$value}"); |
200
|
|
|
break; |
201
|
|
|
case 'process': |
202
|
|
|
throw new DumperException("{$value} is not defined in " . self::PROCESSES_LABEL); |
203
|
|
|
break; |
204
|
|
|
case 'no_definition': |
205
|
|
|
throw new DumperException("Defintion has " . |
206
|
|
|
self::INITIALIZERS_LABEL . " but no {$value} node" |
207
|
|
|
); |
208
|
|
|
break; |
|
|
|
|
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
} |
212
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.