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
|
|
|
namespace PhpFlo\Fbp; |
11
|
|
|
|
12
|
|
|
use PhpFlo\Common\FbpDefinitionsInterface; |
13
|
|
|
use PhpFlo\Exception\ParserDefinitionException; |
14
|
|
|
use PhpFlo\Exception\ParserException; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Class FbpParser |
18
|
|
|
* |
19
|
|
|
* @package PhpFlo\Parser |
20
|
|
|
* @author Marc Aschmann <[email protected]> |
21
|
|
|
*/ |
22
|
|
|
final class FbpParser implements FbpDefinitionsInterface |
23
|
|
|
{ |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var string |
27
|
|
|
*/ |
28
|
|
|
private $source; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
private $settings; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var array |
37
|
|
|
*/ |
38
|
|
|
private $schema; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var int |
42
|
|
|
*/ |
43
|
|
|
private $linecount; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var array |
47
|
|
|
*/ |
48
|
|
|
private $definition; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* FbpParser constructor. |
52
|
|
|
* |
53
|
|
|
* @param string $source optional for initializing |
54
|
|
|
* @param array $settings optional settings for parser |
55
|
|
|
*/ |
56
|
6 |
|
public function __construct($source = '', $settings = []) |
57
|
|
|
{ |
58
|
6 |
|
$this->source = $source; |
59
|
6 |
|
$this->settings = array_replace_recursive( |
60
|
6 |
|
[], |
61
|
|
|
$settings |
62
|
6 |
|
); |
63
|
|
|
|
64
|
6 |
|
$this->schema = [ |
65
|
6 |
|
self::PROPERTIES_LABEL => [ |
66
|
6 |
|
'name' => '', |
67
|
6 |
|
], |
68
|
6 |
|
self::INITIALIZERS_LABEL => [], |
69
|
6 |
|
self::PROCESSES_LABEL => [], |
70
|
6 |
|
self::CONNECTIONS_LABEL => [], |
71
|
|
|
]; |
72
|
|
|
|
73
|
6 |
|
$this->definition = []; |
74
|
6 |
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @param string $source |
78
|
|
|
* @return FbpDefinition |
79
|
|
|
* @throws ParserException |
80
|
|
|
*/ |
81
|
6 |
|
public function run($source = '') |
82
|
|
|
{ |
83
|
6 |
|
if ('' != $source) { |
84
|
1 |
|
$this->source = $source; |
85
|
1 |
|
} |
86
|
|
|
|
87
|
6 |
|
if (empty($this->source)) { |
88
|
1 |
|
throw new ParserException("FbpParser::run(): no source data or empty string given!"); |
89
|
|
|
} |
90
|
|
|
|
91
|
5 |
|
$this->definition = $this->schema; // reset |
92
|
5 |
|
$this->linecount = 1; |
93
|
|
|
|
94
|
|
|
/* |
95
|
|
|
* split by lines, OS-independent |
96
|
|
|
* work each line and parse for definitions |
97
|
|
|
*/ |
98
|
5 |
|
foreach (preg_split('/' . self::NEWLINES . '/m', $this->source) as $line) { |
99
|
5 |
|
$subset = $this->examineSubset($line); |
100
|
5 |
|
$this->definition[self::CONNECTIONS_LABEL] = array_merge_recursive( |
101
|
5 |
|
$this->definition[self::CONNECTIONS_LABEL], $subset |
102
|
5 |
|
); |
103
|
5 |
|
$this->linecount++; |
104
|
5 |
|
} |
105
|
|
|
|
106
|
5 |
|
return new FbpDefinition($this->definition); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @param string $line |
111
|
|
|
* @return array |
112
|
|
|
* @throws ParserDefinitionException |
113
|
|
|
*/ |
114
|
5 |
|
private function examineSubset($line) |
115
|
|
|
{ |
116
|
5 |
|
$subset = []; |
117
|
5 |
|
$nextSrc = null; |
118
|
5 |
|
$hasInitializer = false; |
119
|
|
|
|
120
|
5 |
|
if (1 == $this->linecount && 0 === strpos(trim($line), "'")) { |
121
|
2 |
|
$hasInitializer = true; |
122
|
2 |
|
} |
123
|
|
|
|
124
|
|
|
// subset |
125
|
5 |
|
foreach (explode(self::SOURCE_TARGET_SEPARATOR, $line) as $definition) { |
126
|
5 |
|
$resolved = []; |
127
|
|
|
|
128
|
5 |
|
if (!$hasInitializer) { |
129
|
5 |
|
$resolved = $this->examineDefinition($definition); |
130
|
5 |
|
} |
131
|
|
|
|
132
|
5 |
|
$hasInport = $this->hasValue($resolved, self::INPORT_LABEL); |
133
|
5 |
|
$hasOutport = $this->hasValue($resolved, self::OUTPORT_LABEL); |
134
|
|
|
|
135
|
|
|
//define states |
136
|
5 |
|
switch (true) { |
137
|
5 |
View Code Duplication |
case !empty($step[self::DATA_LABEL]) && ($hasInport && $hasOutport): |
|
|
|
|
138
|
|
|
// initializer + inport |
139
|
1 |
|
$nextSrc = $resolved; |
140
|
1 |
|
$step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL); |
141
|
|
|
// multi def oneliner initializer resolved |
142
|
1 |
|
array_push($this->definition[self::INITIALIZERS_LABEL], $step); |
|
|
|
|
143
|
1 |
|
$step = []; |
144
|
1 |
|
break; |
145
|
5 |
|
case !empty($nextSrc) && ($hasInport && $hasOutport): |
146
|
|
|
// if there was an initializer, we get a full touple with this iteration |
147
|
|
|
$step = [ |
148
|
1 |
|
self::SOURCE_LABEL => $this->addPort($nextSrc, self::OUTPORT_LABEL), |
149
|
1 |
|
self::TARGET_LABEL => $this->addPort($resolved, self::INPORT_LABEL), |
150
|
1 |
|
]; |
151
|
1 |
|
$nextSrc = $resolved; |
152
|
1 |
|
array_push($subset, $step); |
153
|
1 |
|
$step = []; |
154
|
1 |
|
break; |
155
|
5 |
View Code Duplication |
case $hasInport && $hasOutport: |
|
|
|
|
156
|
|
|
// tgt + multi def |
157
|
2 |
|
$nextSrc = $resolved; |
158
|
2 |
|
$step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL); |
159
|
|
|
// check if we've already got the touple ready |
160
|
2 |
|
if (!empty($step[self::SOURCE_LABEL])) { |
161
|
2 |
|
array_push($subset, $step); |
162
|
2 |
|
$step = []; |
163
|
2 |
|
} |
164
|
2 |
|
break; |
165
|
5 |
|
case $hasInport && $nextSrc: |
166
|
|
|
// use orevious OUT as src to fill touple |
167
|
2 |
|
$step[self::SOURCE_LABEL] = $this->addPort($nextSrc, self::OUTPORT_LABEL); |
168
|
2 |
|
$nextSrc = null; |
|
|
|
|
169
|
5 |
|
case $hasInport: |
170
|
5 |
|
$step[self::TARGET_LABEL] = $this->addPort($resolved, self::INPORT_LABEL); |
171
|
|
|
// resolved touple |
172
|
5 |
|
if (empty($step[self::DATA_LABEL])) { |
173
|
5 |
|
array_push($subset, $step); |
174
|
5 |
|
} else { |
175
|
1 |
|
array_push($this->definition[self::INITIALIZERS_LABEL], $step); |
176
|
|
|
} |
177
|
5 |
|
$nextSrc = null; |
178
|
5 |
|
$step = []; |
179
|
5 |
|
break; |
180
|
5 |
|
case $hasOutport: |
181
|
|
|
// simplest case OUT -> IN |
182
|
5 |
|
$step[self::SOURCE_LABEL] = $this->addPort($resolved, self::OUTPORT_LABEL); |
183
|
5 |
|
break; |
184
|
2 |
|
case $hasInitializer: |
185
|
|
|
// initialization value: at the moment we only support one |
186
|
2 |
|
$step[self::DATA_LABEL] = trim($definition, " '"); |
187
|
2 |
|
$hasInitializer = false; // reset |
188
|
2 |
|
break; |
189
|
|
|
default: |
190
|
|
|
throw new ParserDefinitionException( |
191
|
|
|
"Line ({$this->linecount}) {$line} does not contain in or out ports!" |
192
|
|
|
); |
193
|
|
|
} |
194
|
5 |
|
} |
195
|
|
|
|
196
|
5 |
|
return $subset; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Check if array has a specific key and is not empty. |
201
|
|
|
* |
202
|
|
|
* @param array $check |
203
|
|
|
* @param string $value |
204
|
|
|
* @return bool |
205
|
|
|
*/ |
206
|
5 |
|
private function hasValue(array $check, $value) |
207
|
|
|
{ |
208
|
5 |
|
if (empty($check[$value])) { |
209
|
5 |
|
return false; |
210
|
|
|
} |
211
|
|
|
|
212
|
5 |
|
return true; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @param array $definition |
217
|
|
|
* @param string $label |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
5 |
|
private function addPort(array $definition, $label) |
221
|
|
|
{ |
222
|
|
|
return [ |
223
|
5 |
|
self::PROCESS_LABEL => $definition[self::PROCESS_LABEL], |
224
|
5 |
|
self::PORT_LABEL => $definition[$label], |
225
|
5 |
|
]; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @param string $line |
230
|
|
|
* @return array |
231
|
|
|
* @throws ParserDefinitionException |
232
|
|
|
*/ |
233
|
5 |
|
private function examineDefinition($line) |
234
|
|
|
{ |
235
|
5 |
|
preg_match('/' . self::PROCESS_DEFINITION . '/', $line, $matches); |
236
|
5 |
|
foreach ($matches as $key => $value) { |
237
|
5 |
|
if (is_numeric($key)) { |
238
|
5 |
|
unset($matches[$key]); |
239
|
5 |
|
} |
240
|
5 |
|
} |
241
|
|
|
|
242
|
5 |
|
if (!empty($matches[self::PROCESS_LABEL])) { |
243
|
5 |
|
if (empty($matches[self::COMPONENT_LABEL])) { |
244
|
4 |
|
$matches[self::COMPONENT_LABEL] = $matches[self::PROCESS_LABEL]; |
245
|
4 |
|
} |
246
|
|
|
|
247
|
5 |
|
$this->examineProcess($matches); |
248
|
5 |
|
} else { |
249
|
|
|
throw new ParserDefinitionException( |
250
|
|
|
"No process definition found in line ({$this->linecount}) {$line}" |
251
|
|
|
); |
252
|
|
|
} |
253
|
|
|
|
254
|
5 |
|
return $matches; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Add entry to processes. |
259
|
|
|
* |
260
|
|
|
* @param array $process |
261
|
|
|
*/ |
262
|
5 |
|
private function examineProcess(array $process) |
263
|
|
|
{ |
264
|
5 |
|
if (!isset($this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]])) { |
265
|
5 |
|
$component = $process[self::COMPONENT_LABEL]; |
266
|
5 |
|
if (empty($component)) { |
267
|
|
|
$component = $process[self::PROCESS_LABEL]; |
268
|
|
|
} |
269
|
|
|
|
270
|
5 |
|
$this->definition[self::PROCESSES_LABEL][$process[self::PROCESS_LABEL]] = [ |
271
|
5 |
|
self::COMPONENT_LABEL => $component, |
272
|
5 |
|
self::METADATA_LABEL => [ |
273
|
5 |
|
'label' => $component, |
274
|
5 |
|
], |
275
|
|
|
]; |
276
|
5 |
|
} |
277
|
5 |
|
} |
278
|
|
|
} |
279
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.