1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link https://github.com/old-town/old-town-workflow |
4
|
|
|
* @author Malofeykin Andrey <[email protected]> |
5
|
|
|
*/ |
6
|
|
|
namespace OldTown\Workflow\Loader; |
7
|
|
|
|
8
|
|
|
use OldTown\Workflow\Exception\FactoryException; |
9
|
|
|
use OldTown\Workflow\Exception\InvalidParsingWorkflowException; |
10
|
|
|
use OldTown\Workflow\Exception\InvalidWriteWorkflowException; |
11
|
|
|
use OldTown\Workflow\Exception\UnsupportedOperationException; |
12
|
|
|
use OldTown\Workflow\Util\Properties\Properties; |
13
|
|
|
use Serializable; |
14
|
|
|
use OldTown\Workflow\Loader\XMLWorkflowFactory\WorkflowConfig; |
15
|
|
|
use DOMElement; |
16
|
|
|
use DOMDocument; |
17
|
|
|
use OldTown\Workflow\Exception\RuntimeException; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Class UrlWorkflowFactory |
21
|
|
|
* |
22
|
|
|
* @package OldTown\Workflow\Loader |
23
|
|
|
*/ |
24
|
|
|
class XmlWorkflowFactory extends AbstractWorkflowFactory implements Serializable |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* |
28
|
|
|
* @var string |
29
|
|
|
*/ |
30
|
|
|
const RESOURCE_PROPERTY = 'resource'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
const RELOAD_PROPERTY = 'reload'; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var WorkflowConfig[] |
40
|
|
|
*/ |
41
|
|
|
protected $workflows = []; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var bool |
45
|
|
|
*/ |
46
|
|
|
protected $reload = false; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Пути по умолчнаию до файла с workflows |
50
|
|
|
* |
51
|
|
|
* @var array |
52
|
|
|
*/ |
53
|
|
|
protected static $defaultPathsToWorkflows = []; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @param Properties $p |
57
|
|
|
*/ |
58
|
23 |
|
public function __construct(Properties $p = null) |
59
|
|
|
{ |
60
|
23 |
|
parent::__construct($p); |
61
|
23 |
|
$this->initDefaultPathsToWorkflows(); |
62
|
23 |
|
} |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @param string $workflowName |
67
|
|
|
* @param string $layout |
68
|
|
|
* |
69
|
|
|
* @return $this |
70
|
|
|
*/ |
71
|
1 |
|
public function setLayout($workflowName, $layout) |
72
|
|
|
{ |
73
|
1 |
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @param string $workflowName |
77
|
|
|
* |
78
|
|
|
* @return mixed|null |
79
|
|
|
*/ |
80
|
1 |
|
public function getLayout($workflowName) |
81
|
|
|
{ |
82
|
1 |
|
return null; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* |
88
|
|
|
* @return string |
89
|
|
|
*/ |
90
|
1 |
|
public function getName() |
91
|
|
|
{ |
92
|
1 |
|
return ''; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @param string $name |
97
|
|
|
* |
98
|
|
|
* @return boolean |
99
|
|
|
*/ |
100
|
1 |
|
public function isModifiable($name) |
101
|
|
|
{ |
102
|
1 |
|
return true; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* String representation of object |
107
|
|
|
* |
108
|
|
|
* @link http://php.net/manual/en/serializable.serialize.php |
109
|
|
|
* @return string the string representation of the object or null |
110
|
|
|
*/ |
111
|
1 |
|
public function serialize() |
112
|
|
|
{ |
113
|
1 |
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Constructs the object |
117
|
|
|
* |
118
|
|
|
* @link http://php.net/manual/en/serializable.unserialize.php |
119
|
|
|
* |
120
|
|
|
* @param string $serialized <p> |
121
|
|
|
* |
122
|
|
|
* @return void |
123
|
|
|
*/ |
124
|
1 |
|
public function unserialize($serialized) |
125
|
|
|
{ |
126
|
1 |
|
} |
127
|
|
|
/** |
128
|
|
|
* |
129
|
|
|
* @return String[] |
130
|
|
|
* @throws FactoryException |
131
|
|
|
*/ |
132
|
1 |
|
public function getWorkflowNames() |
133
|
|
|
{ |
134
|
1 |
|
$workflowNames = array_keys($this->workflows); |
135
|
|
|
|
136
|
1 |
|
return $workflowNames; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @param string $name |
141
|
|
|
* |
142
|
|
|
* @return boolean |
143
|
|
|
* @throws FactoryException |
144
|
|
|
*/ |
145
|
1 |
|
public function removeWorkflow($name) |
146
|
|
|
{ |
147
|
1 |
|
throw new FactoryException('Удаление workflow не поддерживается'); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @param string $oldName |
152
|
|
|
* @param string $newName |
153
|
|
|
* |
154
|
|
|
* @return void |
155
|
|
|
*/ |
156
|
1 |
|
public function renameWorkflow($newName, $oldName = null) |
157
|
|
|
{ |
158
|
1 |
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @return void |
162
|
|
|
*/ |
163
|
1 |
|
public function save() |
164
|
|
|
{ |
165
|
1 |
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @param string $name |
169
|
|
|
* |
170
|
|
|
* @return void |
171
|
|
|
* @throws FactoryException |
172
|
|
|
*/ |
173
|
1 |
|
public function createWorkflow($name) |
174
|
|
|
{ |
175
|
1 |
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Иницализация путей по которым происходит поиск |
179
|
|
|
* |
180
|
|
|
* @return void |
181
|
|
|
*/ |
182
|
23 |
|
protected function initDefaultPathsToWorkflows() |
183
|
|
|
{ |
184
|
23 |
|
static::$defaultPathsToWorkflows[] = __DIR__ . '/../../config'; |
185
|
23 |
|
} |
186
|
|
|
|
187
|
|
|
|
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* |
191
|
|
|
* @return void |
192
|
|
|
* @throws FactoryException |
193
|
|
|
* @throws InvalidParsingWorkflowException |
194
|
|
|
*/ |
195
|
11 |
|
public function initDone() |
196
|
|
|
{ |
197
|
11 |
|
$this->reload = 'true' === $this->getProperties()->getProperty(static::RELOAD_PROPERTY, 'false'); |
198
|
|
|
|
199
|
11 |
|
$name = $this->getProperties()->getProperty(static::RESOURCE_PROPERTY, 'workflows.xml'); |
200
|
|
|
|
201
|
11 |
|
$pathWorkflowFile = $this->getPathWorkflowFile($name); |
202
|
10 |
|
$content = file_get_contents($pathWorkflowFile); |
203
|
|
|
|
204
|
|
|
try { |
205
|
10 |
|
libxml_use_internal_errors(true); |
206
|
|
|
|
207
|
10 |
|
libxml_clear_errors(); |
208
|
|
|
|
209
|
10 |
|
$xmlDoc = new DOMDocument(); |
210
|
10 |
|
$xmlDoc->loadXML($content); |
211
|
|
|
|
212
|
10 |
|
if ($error = libxml_get_last_error()) { |
213
|
1 |
|
$errMsg = "Error in workflow xml.\n"; |
214
|
1 |
|
$errMsg .= "Message: {$error->message}.\n"; |
215
|
1 |
|
$errMsg .= "File: {$error->file}.\n"; |
216
|
1 |
|
$errMsg .= "Line: {$error->line}.\n"; |
217
|
1 |
|
$errMsg .= "Column: {$error->column}."; |
218
|
|
|
|
219
|
1 |
|
throw new InvalidParsingWorkflowException($errMsg); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** @var DOMElement $root */ |
223
|
9 |
|
$root = $xmlDoc->getElementsByTagName('workflows')->item(0); |
224
|
|
|
|
225
|
9 |
|
$basedir = $this->getBaseDir($root, $pathWorkflowFile); |
226
|
|
|
|
227
|
8 |
|
$list = XmlUtil::getChildElements($root, 'workflow'); |
228
|
|
|
|
229
|
|
|
|
230
|
8 |
|
foreach ($list as $e) { |
231
|
8 |
|
$type = XmlUtil::getRequiredAttributeValue($e, 'type'); |
232
|
8 |
|
$location = XmlUtil::getRequiredAttributeValue($e, 'location'); |
233
|
8 |
|
$config = $this->buildWorkflowConfig($basedir, $type, $location); |
234
|
8 |
|
$name = XmlUtil::getRequiredAttributeValue($e, 'name'); |
235
|
8 |
|
$this->workflows[$name] = $config; |
236
|
8 |
|
} |
237
|
10 |
|
} catch (\Exception $e) { |
238
|
2 |
|
$errMsg = sprintf( |
239
|
2 |
|
'Ошибка в конфигурации workflow: %s', |
240
|
2 |
|
$e->getMessage() |
241
|
2 |
|
); |
242
|
2 |
|
throw new InvalidParsingWorkflowException($errMsg, $e->getCode(), $e); |
243
|
|
|
} |
244
|
8 |
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param $basedir |
248
|
|
|
* @param $type |
249
|
|
|
* @param $location |
250
|
|
|
* |
251
|
|
|
* @return WorkflowConfig |
252
|
|
|
* @throws \OldTown\Workflow\Exception\RemoteException |
253
|
|
|
*/ |
254
|
7 |
|
protected function buildWorkflowConfig($basedir, $type, $location) |
255
|
|
|
{ |
256
|
7 |
|
$config = new WorkflowConfig($basedir, $type, $location); |
257
|
|
|
|
258
|
7 |
|
return $config; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @param string $name |
263
|
|
|
* @param bool $validate |
264
|
|
|
* |
265
|
|
|
* @return WorkflowDescriptor |
266
|
|
|
* @throws FactoryException |
267
|
|
|
*/ |
268
|
9 |
|
public function getWorkflow($name, $validate = true) |
269
|
|
|
{ |
270
|
9 |
|
$name = (string)$name; |
271
|
9 |
View Code Duplication |
if (!array_key_exists($name, $this->workflows)) { |
|
|
|
|
272
|
1 |
|
$errMsg = sprintf('Нет workflow с именем %s', $name); |
273
|
1 |
|
throw new FactoryException($errMsg); |
274
|
|
|
} |
275
|
8 |
|
$c = $this->workflows[$name]; |
276
|
|
|
|
277
|
8 |
|
if (null !== $c->descriptor) { |
278
|
1 |
|
if ($this->reload && file_exists($c->url) && (filemtime($c->url) > $c->lastModified)) { |
279
|
1 |
|
$c->lastModified = filemtime($c->url); |
280
|
1 |
|
$this->loadWorkflow($c, $validate); |
281
|
1 |
|
} |
282
|
1 |
|
} else { |
283
|
8 |
|
$this->loadWorkflow($c, $validate); |
284
|
|
|
} |
285
|
|
|
|
286
|
7 |
|
$c->descriptor->setName($name); |
287
|
|
|
|
288
|
7 |
|
return $c->descriptor; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* @param WorkflowConfig $c |
293
|
|
|
* @param boolean $validate |
294
|
|
|
* |
295
|
|
|
* @return void |
296
|
|
|
* @throws FactoryException |
297
|
|
|
*/ |
298
|
8 |
|
protected function loadWorkflow(WorkflowConfig $c, $validate = true) |
299
|
|
|
{ |
300
|
8 |
|
$validate = (boolean)$validate; |
301
|
|
|
try { |
302
|
8 |
|
$c->descriptor = WorkflowLoader::load($c->url, $validate); |
303
|
8 |
|
} catch (\Exception $e) { |
304
|
1 |
|
$errMsg = "Некорректный дескрипторв workflow: {$c->url}"; |
305
|
1 |
|
throw new FactoryException($errMsg, $e->getCode(), $e); |
306
|
|
|
} |
307
|
7 |
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Возвращает абсолютный путь до директории где находится workflow xml файл |
311
|
|
|
* |
312
|
|
|
* @param DOMElement $root |
313
|
|
|
* |
314
|
|
|
* @param $pathWorkflowFile |
315
|
|
|
* |
316
|
|
|
* @return string |
317
|
|
|
* @throws RuntimeException |
318
|
|
|
*/ |
319
|
9 |
|
protected function getBaseDir(DOMElement $root, $pathWorkflowFile) |
320
|
|
|
{ |
321
|
9 |
|
if (!$root->hasAttribute('basedir')) { |
322
|
1 |
|
return null; |
323
|
|
|
} |
324
|
8 |
|
$basedirAtr = XmlUtil::getRequiredAttributeValue($root, 'basedir'); |
325
|
|
|
|
326
|
|
|
|
327
|
8 |
|
$basedir = $basedirAtr; |
328
|
8 |
|
if (0 === strpos($basedir, '.')) { |
329
|
8 |
|
$basedir = dirname($pathWorkflowFile) . substr($basedir, 1); |
330
|
8 |
|
} |
331
|
|
|
|
332
|
8 |
|
if (file_exists($basedir)) { |
333
|
7 |
|
$absolutePath = realpath($basedir); |
334
|
7 |
|
} else { |
335
|
1 |
|
$errMsg = sprintf('Отсутствует ресурс %s', $basedirAtr); |
336
|
1 |
|
throw new RuntimeException($errMsg); |
337
|
|
|
} |
338
|
|
|
|
339
|
7 |
|
return $absolutePath; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
|
343
|
|
|
|
344
|
|
|
|
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* @param string $name |
348
|
|
|
* |
349
|
|
|
* @return string |
350
|
|
|
* @throws FactoryException |
351
|
|
|
*/ |
352
|
10 |
|
protected function getPathWorkflowFile($name) |
353
|
|
|
{ |
354
|
10 |
|
$paths = static::getDefaultPathsToWorkflows(); |
355
|
|
|
|
356
|
10 |
|
$pathWorkflowFile = null; |
357
|
10 |
|
foreach ($paths as $path) { |
358
|
10 |
|
$path = realpath($path); |
359
|
10 |
|
if ($path) { |
360
|
10 |
|
$filePath = $path . DIRECTORY_SEPARATOR . $name; |
361
|
10 |
|
if (file_exists($filePath)) { |
362
|
9 |
|
$pathWorkflowFile = $filePath; |
363
|
9 |
|
break; |
364
|
|
|
} |
365
|
1 |
|
} |
366
|
10 |
|
} |
367
|
|
|
|
368
|
10 |
|
if (null === $pathWorkflowFile) { |
369
|
1 |
|
$errMsg = 'Не удалось найти файл workflow'; |
370
|
1 |
|
throw new FactoryException($errMsg); |
371
|
|
|
} |
372
|
|
|
|
373
|
9 |
|
return $pathWorkflowFile; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* @return array |
378
|
|
|
*/ |
379
|
23 |
|
public static function getDefaultPathsToWorkflows() |
380
|
|
|
{ |
381
|
23 |
|
return static::$defaultPathsToWorkflows; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* @param array $defaultPathsToWorkflows |
386
|
|
|
*/ |
387
|
23 |
|
public static function setDefaultPathsToWorkflows(array $defaultPathsToWorkflows = []) |
388
|
|
|
{ |
389
|
23 |
|
static::$defaultPathsToWorkflows = $defaultPathsToWorkflows; |
390
|
23 |
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* @param string $path |
394
|
|
|
*/ |
395
|
9 |
|
public static function addDefaultPathToWorkflows($path) |
396
|
|
|
{ |
397
|
9 |
|
$path = (string)$path; |
398
|
|
|
|
399
|
9 |
|
array_unshift(static::$defaultPathsToWorkflows, $path); |
400
|
9 |
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Сохраняет workflow |
404
|
|
|
* |
405
|
|
|
* @param string $name имя workflow |
406
|
|
|
* @param WorkflowDescriptor $descriptor descriptor workflow |
407
|
|
|
* @param boolean $replace если true - то в случае существования одноименного workflow, оно будет |
408
|
|
|
* заменено |
409
|
|
|
* |
410
|
|
|
* @return boolean true - если workflow было сохранено |
411
|
|
|
* @throws FactoryException |
412
|
|
|
* @throws UnsupportedOperationException |
413
|
|
|
* @throws InvalidWriteWorkflowException |
414
|
|
|
*/ |
415
|
5 |
|
public function saveWorkflow($name, WorkflowDescriptor $descriptor, $replace = false) |
416
|
|
|
{ |
417
|
5 |
|
$name = (string)$name; |
418
|
5 |
|
$c = array_key_exists($name, $this->workflows) ? $this->workflows[$name] : null; |
419
|
|
|
|
420
|
5 |
|
if (null !== $c && !$replace) { |
421
|
1 |
|
return false; |
422
|
|
|
} |
423
|
|
|
|
424
|
4 |
|
if (null === $c) { |
425
|
1 |
|
$errMsg = 'Сохранение workflow не поддерживается'; |
426
|
1 |
|
throw new UnsupportedOperationException($errMsg); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
try { |
430
|
3 |
|
$content = $descriptor->writeXml(); |
431
|
3 |
|
$newFileName = $c->url . '.new'; |
432
|
3 |
|
$content->save($newFileName); |
433
|
|
|
|
434
|
|
|
|
435
|
3 |
|
$bakFileName = $c->url . '.bak'; |
436
|
3 |
|
$isOk = $this->createBackupFile($c->url, $bakFileName); |
437
|
|
|
|
438
|
3 |
|
if (!$isOk) { |
439
|
1 |
|
$errMsg = sprintf( |
440
|
1 |
|
'Ошибка при архивирование оригинального файла workflow %s в %s - сохранение прервано', |
441
|
1 |
|
$c->url, |
442
|
|
|
$bakFileName |
443
|
1 |
|
); |
444
|
1 |
|
throw new FactoryException($errMsg); |
445
|
|
|
} |
446
|
2 |
|
$isOk = $this->createNewWorkflowFile($newFileName, $c->url); |
447
|
|
|
|
448
|
2 |
|
if (!$isOk) { |
449
|
1 |
|
$errMsg = sprintf( |
450
|
1 |
|
'Ошибка при переименовывание нового файла workflow %s в %s - сохранение прервано', |
451
|
1 |
|
$newFileName, |
452
|
1 |
|
$c->url |
453
|
1 |
|
); |
454
|
1 |
|
throw new FactoryException($errMsg); |
455
|
|
|
} |
456
|
|
|
|
457
|
1 |
|
unlink($bakFileName); |
458
|
|
|
|
459
|
1 |
|
return true; |
460
|
2 |
|
} catch (\Exception $e) { |
461
|
2 |
|
throw new InvalidWriteWorkflowException($e->getMessage(), $e->getCode(), $e); |
462
|
|
|
} |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Архивирование оригинального файла workflow |
467
|
|
|
* |
468
|
|
|
* @param $original |
469
|
|
|
* @param $backup |
470
|
|
|
* |
471
|
|
|
* @return bool |
472
|
|
|
*/ |
473
|
2 |
|
protected function createBackupFile($original, $backup) |
474
|
|
|
{ |
475
|
2 |
|
$isOk = !file_exists($original) || rename($original, $backup); |
476
|
|
|
|
477
|
2 |
|
return $isOk; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
/** |
481
|
|
|
* @param $newFileName |
482
|
|
|
* @param $targetFile |
483
|
|
|
* |
484
|
|
|
* @return bool |
485
|
|
|
*/ |
486
|
1 |
|
protected function createNewWorkflowFile($newFileName, $targetFile) |
487
|
|
|
{ |
488
|
1 |
|
$isOk = rename($newFileName, $targetFile); |
489
|
|
|
|
490
|
1 |
|
return $isOk; |
491
|
|
|
} |
492
|
|
|
} |
493
|
|
|
|
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.