GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — dev ( 925885...2441e9 )
by Андрей
06:27
created

AbstractWorkflow::doAction()   C

Complexity

Conditions 9
Paths 52

Size

Total Lines 60
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 10.5643

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 60
ccs 30
cts 41
cp 0.7317
rs 6.8358
cc 9
eloc 37
nc 52
nop 3
crap 10.5643

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow;
7
8
use OldTown\Log\LogFactory;
9
use OldTown\PropertySet\PropertySetInterface;
10
use OldTown\PropertySet\PropertySetManager;
11
use OldTown\Workflow\Config\ConfigurationInterface;
12
use OldTown\Workflow\Config\DefaultConfiguration;
13
use OldTown\Workflow\Exception\FactoryException;
14
use OldTown\Workflow\Exception\InternalWorkflowException;
15
use OldTown\Workflow\Exception\InvalidActionException;
16
use OldTown\Workflow\Exception\InvalidArgumentException;
17
use OldTown\Workflow\Exception\InvalidEntryStateException;
18
use OldTown\Workflow\Exception\InvalidRoleException;
19
use OldTown\Workflow\Exception\StoreException;
20
use OldTown\Workflow\Exception\WorkflowException;
21
use OldTown\Workflow\Loader\ActionDescriptor;
22
use OldTown\Workflow\Loader\PermissionDescriptor;
23
use OldTown\Workflow\Loader\WorkflowDescriptor;
24
use OldTown\Workflow\Query\WorkflowExpressionQuery;
25
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
26
use OldTown\Workflow\Spi\StepInterface;
27
use OldTown\Workflow\Spi\WorkflowEntryInterface;
28
use OldTown\Workflow\Spi\WorkflowStoreInterface;
29
use OldTown\Workflow\TransientVars\TransientVarsInterface;
30
use Psr\Log\LoggerInterface;
31
use SplObjectStorage;
32
use OldTown\Workflow\TransientVars\BaseTransientVars;
33
use ReflectionClass;
34
use ArrayObject;
35
use OldTown\Workflow\Engine\EngineManagerInterface;
36
use OldTown\Workflow\Engine\EngineManager;
37
38
39
40
/**
41
 * Class AbstractWorkflow
42
 *
43
 * @package OldTown\Workflow
44
 */
45
abstract class  AbstractWorkflow implements WorkflowInterface
46
{
47
    /**
48
     * @var string
49
     */
50
    const CURRENT_STEPS = 'currentSteps';
51
52
    /**
53
     * @var string
54
     */
55
    const HISTORY_STEPS = 'historySteps';
56
57
    /**
58
     * @var WorkflowContextInterface
59
     */
60
    protected $context;
61
62
    /**
63
     * @var ConfigurationInterface
64
     */
65
    protected $configuration;
66
67
68
    /**
69
     * @var TypeResolverInterface
70
     */
71
    protected $typeResolver;
72
73
    /**
74
     * Логер
75
     *
76
     * @var LoggerInterface
77
     */
78
    protected $log;
79
80
    /**
81
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
82
     *
83
     * @var string
84
     */
85
    protected $defaultTypeResolverClass = TypeResolver::class;
86
87
    /**
88
     * Карта переходов состояния процесса workflow
89
     *
90
     * @var null|array
91
     */
92
    protected $mapEntryState;
93
94
    /**
95
     * Менеджер движков
96
     *
97
     * @var EngineManagerInterface
98
     */
99
    protected $engineManager;
100
101
    /**
102
     * AbstractWorkflow constructor.
103
     *
104
     * @throws InternalWorkflowException
105
     */
106 19
    public function __construct()
107
    {
108 19
        $this->initLoger();
109 19
        $this->initMapEntryState();
110 19
    }
111
112
    /**
113
     * Устанавливает менеджер движков
114
     *
115
     * @return EngineManagerInterface
116
     */
117 19
    public function getEngineManager()
118
    {
119 19
        if ($this->engineManager) {
120 19
            return $this->engineManager;
121
        }
122 19
        $this->engineManager = new EngineManager($this);
123
124 19
        return $this->engineManager;
125
    }
126
127
    /**
128
     * Возвращает менеджер движков
129
     *
130
     * @param EngineManagerInterface $engineManager
131
     *
132
     * @return $this
133
     */
134
    public function setEngineManager(EngineManagerInterface $engineManager)
135
    {
136
        $this->engineManager = $engineManager;
137
138
        return $this;
139
    }
140
141
    /**
142
     * Инициация карты переходов состояния процесса workflow
143
     */
144 19
    protected function initMapEntryState()
145
    {
146 19
        $this->mapEntryState = [
147 19
            WorkflowEntryInterface::COMPLETED => [
148 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
149 19
            ],
150 19
            WorkflowEntryInterface::CREATED => [],
151 19
            WorkflowEntryInterface::ACTIVATED => [
152 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED,
153 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
154
155 19
            ],
156 19
            WorkflowEntryInterface::SUSPENDED => [
157 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
158 19
            ],
159 19
            WorkflowEntryInterface::KILLED => [
160 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
161 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED,
162 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED
163 19
            ]
164 19
        ];
165 19
    }
166
167
    /**
168
     * Инициализация системы логирования
169
     *
170
     * @throws InternalWorkflowException
171
     */
172 19
    protected function initLoger()
173
    {
174 1
        try {
175 19
            $this->log = LogFactory::getLog();
176 19
        } catch (\Exception $e) {
177
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
178
        }
179 19
    }
180
181
    /**
182
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
183
     * Workflow может быть инициализированно только один раз
184
     *
185
     * @param string $workflowName Имя workflow
186
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
187
     * @param TransientVarsInterface $inputs Данные введеные пользователем
188
     *
189
     * @return integer
190
     *
191
     * @throws InternalWorkflowException
192
     * @throws InvalidActionException
193
     * @throws InvalidRoleException
194
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
195
     * @throws InvalidArgumentException
196
     * @throws WorkflowException
197
     */
198 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
199
    {
200
        try {
201 19
            $initialAction = (integer)$initialAction;
202
203 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
204
205 19
            $store = $this->getPersistence();
206
207 19
            $entry = $store->createEntry($workflowName);
208
209 19
            $ps = $store->getPropertySet($entry->getId());
210
211
212 19
            if (null === $inputs) {
213
                $inputs = $this->transientVarsFactory();
214
            }
215
216 19
            $engineManager = $this->getEngineManager();
217
218 19
            $transientVars = $inputs;
219 19
            $inputs = clone $transientVars;
220
221 19
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
222
223 19
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
224 1
                $this->context->setRollbackOnly();
225 1
                $errMsg = 'You are restricted from initializing this workflow';
226 1
                throw new InvalidRoleException($errMsg);
227
            }
228
229 18
            $action = $wf->getInitialAction($initialAction);
230
231 18
            if (null === $action) {
232
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
233
                throw new InvalidActionException($errMsg);
234
            }
235
236 18
            $currentSteps = new SplObjectStorage();
237 18
            $transitionManager = $engineManager->getTransitionEngine();
238 18
            $transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
239
240 16
            $entryId = $entry->getId();
241 19
        } catch (WorkflowException $e) {
242
            $this->context->setRollbackOnly();
243
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
244
        }
245
246 16
        return $entryId;
247
    }
248
249
    /**
250
     * @param $id
251
     * @param $inputs
252
     *
253
     * @return array
254
     *
255
     */
256 2
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
257 2
    {
258
        try {
259
            $store = $this->getPersistence();
260
            $entry = $store->findEntry($id);
261
262
            if (null === $entry) {
263
                $errMsg = sprintf(
264
                    'Не существует экземпляра workflow c id %s',
265
                    $id
266
                );
267
                throw new InvalidArgumentException($errMsg);
268
            }
269
270
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
271
                return [];
272
            }
273
274
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
275
276
            $l = [];
277
            $ps = $store->getPropertySet($id);
278
279
            $transientVars = $inputs;
280
            if (null === $transientVars) {
281
                $transientVars = $this->transientVarsFactory();
282
            }
283
284
            $currentSteps = $store->findCurrentSteps($id);
285
286
            $engineManager =  $this->getEngineManager();
287
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
288
289
            $globalActions = $wf->getGlobalActions();
290
291
            $conditionsEngine = $engineManager->getConditionsEngine();
292
            foreach ($globalActions as $action) {
293
                $restriction = $action->getRestriction();
294
                $conditions = null;
295
296
                $transientVars['actionId'] = $action->getId();
297
298
                if (null !== $restriction) {
299
                    $conditions = $restriction->getConditionsDescriptor();
300
                }
301
302
                $flag = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
303
                if ($flag) {
304
                    $l[] = $action->getId();
305
                }
306
            }
307
308
            foreach ($currentSteps as $currentStep) {
309
                $availableActionsForStep = $this->getAvailableActionsForStep($wf, $currentStep, $transientVars, $ps);
310
                foreach ($availableActionsForStep as $actionId) {
311
                    $l[] = $actionId;
312
                }
313
            }
314
315
316
            return array_unique($l);
317
        } catch (\Exception $e) {
318
            $errMsg = 'Ошибка проверки доступных действий';
319
            $this->getLog()->error($errMsg, [$e]);
320
        }
321
322
        return [];
323
    }
324
325
    /**
326
     * Создает хранилище переменных
327
     *
328
     * @param $class
329
     *
330
     * @return TransientVarsInterface
331
     */
332
    protected function transientVarsFactory($class = BaseTransientVars::class)
333
    {
334
        $r = new \ReflectionClass($class);
335
        return $r->newInstance();
336
    }
337
338
    /**
339
     *
340
     *
341
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
342
     *
343
     * @param integer $entryId id запущенного процесса workflow
344
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
345
     * @param TransientVarsInterface $inputs Входные данные для перехода
346
     *
347
     * @return void
348
     *
349
     * @throws WorkflowException
350
     * @throws InvalidActionException
351
     * @throws InvalidArgumentException
352
     * @throws InternalWorkflowException
353
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
354
     */
355 8
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
356
    {
357 8
        $actionId = (integer)$actionId;
358 8
        if (null === $inputs) {
359
            $inputs = $this->transientVarsFactory();
360
        }
361 8
        $transientVars = $inputs;
362 8
        $inputs = clone $transientVars;
363
364 8
        $store = $this->getPersistence();
365 8
        $entry = $store->findEntry($entryId);
366
367 8
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
368
            return;
369
        }
370
371 8
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
372
373 8
        $currentSteps = $store->findCurrentSteps($entryId);
374 8
        if (!$currentSteps instanceof SplObjectStorage) {
375
            $errMsg = 'Invalid currentSteps';
376
            throw new InternalWorkflowException($errMsg);
377
        }
378
379 8
        $action = null;
380
381 8
        $ps = $store->getPropertySet($entryId);
382
383 8
        $engineManager = $this->getEngineManager();
384 8
        $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
385
386 8
        $validGlobalActions = $this->getValidGlobalActions($wf, $actionId, $transientVars, $ps);
387 8
        if ($validGlobalActions) {
388
            $action = $validGlobalActions;
389
        }
390 8
        $validActionsFromCurrentSteps = $this->getValidActionsFromCurrentSteps($currentSteps, $wf, $actionId, $transientVars, $ps);
391 8
        if ($validActionsFromCurrentSteps) {
392 6
            $action = $validActionsFromCurrentSteps;
393 6
        }
394
395 8
        if (null === $action) {
396 2
            $errMsg = sprintf(
397 2
                'Action %s is invalid',
398
                $actionId
399 2
            );
400 2
            throw new InvalidActionException($errMsg);
401
        }
402
403
404
        try {
405 6
            $transitionManager = $this->getEngineManager()->getTransitionEngine();
406 6
            if ($transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps)) {
407
                $this->checkImplicitFinish($action, $entryId);
408
            }
409 6
        } catch (WorkflowException $e) {
410
            $this->context->setRollbackOnly();
411
            /** @var  WorkflowException $e*/
412
            throw $e;
413
        }
414 5
    }
415
416
    /**
417
     * @param WorkflowDescriptor     $wf
418
     * @param                        $actionId
419
     * @param TransientVarsInterface $transientVars
420
     * @param PropertySetInterface   $ps
421
     *
422
     * @return ActionDescriptor|null
423
     */
424 8
    protected function getValidGlobalActions(WorkflowDescriptor $wf, $actionId, TransientVarsInterface $transientVars, PropertySetInterface $ps)
425
    {
426 8
        $resultAction = null;
427 8
        $transitionEngine = $this->getEngineManager()->getTransitionEngine();
428 8
        foreach ($wf->getGlobalActions() as $actionDesc) {
429
            if ($actionId === $actionDesc->getId() && $transitionEngine->isActionAvailable($actionDesc, $transientVars, $ps, 0)) {
430
                $resultAction = $actionDesc;
431
            }
432 8
        }
433
434 8
        return $resultAction;
435
    }
436
437
438
    /**
439
     *
440
     * @param SplObjectStorage       $currentSteps
441
     * @param WorkflowDescriptor     $wf
442
     * @param                        $actionId
443
     * @param TransientVarsInterface $transientVars
444
     * @param PropertySetInterface   $ps
445
     *
446
     * @return ActionDescriptor|null
447
     *
448
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
449
     * @throws InternalWorkflowException
450
     */
451 8
    protected function getValidActionsFromCurrentSteps(SplObjectStorage $currentSteps, WorkflowDescriptor $wf, $actionId, TransientVarsInterface $transientVars, PropertySetInterface $ps)
452
    {
453 8
        $transitionEngine = $this->getEngineManager()->getTransitionEngine();
454 8
        $resultAction = null;
455 8
        foreach ($currentSteps as $step) {
456 8
            if (!$step instanceof StepInterface) {
457
                $errMsg = 'Invalid step';
458
                throw new InternalWorkflowException($errMsg);
459
            }
460 8
            $s = $wf->getStep($step->getStepId());
461
462 8
            foreach ($s->getActions() as $actionDesc) {
463 8
                if (!$actionDesc instanceof ActionDescriptor) {
464
                    $errMsg = 'Invalid action descriptor';
465
                    throw new InternalWorkflowException($errMsg);
466
                }
467
468 8
                if ($actionId === $actionDesc->getId() && $transitionEngine->isActionAvailable($actionDesc, $transientVars, $ps, $s->getId())) {
469 6
                    $resultAction = $actionDesc;
470 6
                }
471 8
            }
472 8
        }
473
474 8
        return $resultAction;
475
    }
476
477
    /**
478
     * @param ActionDescriptor $action
479
     * @param                  $id
480
     *
481
     * @return void
482
     *
483
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
484
     * @throws InvalidArgumentException
485
     * @throws InternalWorkflowException
486
     */
487 8
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
488 8
    {
489
        $store = $this->getPersistence();
490
        $entry = $store->findEntry($id);
491
492
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
493
494
        $currentSteps = $store->findCurrentSteps($id);
495
496
        $isCompleted = $wf->getGlobalActions()->count() === 0;
497
498
        foreach ($currentSteps as $step) {
499
            if ($isCompleted) {
500
                break;
501
            }
502
503
            $stepDes = $wf->getStep($step->getStepId());
504
505
            if ($stepDes->getActions()->count() > 0) {
506
                $isCompleted = true;
507
            }
508
        }
509
510
        if ($isCompleted) {
511
            $entryEngine = $this->getEngineManager()->getEntryEngine();
512
            $entryEngine->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
513
        }
514
    }
515
516
517
518
    /**
519
     *
520
     * Check if the state of the specified workflow instance can be changed to the new specified one.
521
     *
522
     * @param integer $id The workflow instance id.
523
     * @param integer $newState The new state id.
524
     *
525
     * @return boolean true if the state of the workflow can be modified, false otherwise.
526
     *
527
     * @throws InternalWorkflowException
528
     */
529 16
    public function canModifyEntryState($id, $newState)
530
    {
531 16
        $store = $this->getPersistence();
532 16
        $entry = $store->findEntry($id);
533
534 16
        $currentState = $entry->getState();
535
536 16
        return array_key_exists($newState, $this->mapEntryState) && array_key_exists($currentState, $this->mapEntryState[$newState]);
537
    }
538
539
540
    /**
541
     *
542
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
543
     *
544
     * @param integer $entryId id экземпляра workflow
545
     *
546
     * @return SplObjectStorage|StepInterface[]
547
     *
548
     * @throws InternalWorkflowException
549
     */
550
    public function getCurrentSteps($entryId)
551
    {
552
        return $this->getStepFromStorage($entryId, static::CURRENT_STEPS);
553
    }
554
555
    /**
556
     * Возвращает информацию о том в какие шаги, были осуществленны переходы, для процесса workflow с заданным id
557
     *
558
     * @param integer $entryId уникальный идентификатор процесса workflow
559
     *
560
     * @return StepInterface[]|SplObjectStorage список шагов
561
     *
562
     * @throws InternalWorkflowException
563
     */
564
    public function getHistorySteps($entryId)
565
    {
566
        return $this->getStepFromStorage($entryId, static::HISTORY_STEPS);
567
    }
568
569
    /**
570
     * Получение шагов информации о шагах процесса workflow
571
     *
572
     * @param $entryId
573
     * @param $type
574
     *
575
     * @return Spi\StepInterface[]|SplObjectStorage
576
     *
577
     * @throws InternalWorkflowException
578
     */
579 19
    protected function getStepFromStorage($entryId, $type)
580
    {
581
        try {
582
            $store = $this->getPersistence();
583
584
            if (static::CURRENT_STEPS === $type) {
585
                return $store->findCurrentSteps($entryId);
586
            } elseif (static::HISTORY_STEPS === $type) {
587
                return $store->findHistorySteps($entryId);
588 19
            }
589
        } catch (StoreException $e) {
590
            $errMsg = sprintf(
591
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
592
                $entryId
593
            );
594
            $this->getLog()->error($errMsg, [$e]);
595
        }
596
597
        return new SplObjectStorage();
598
    }
599
600
601
602
    /**
603
     *
604
     *
605
     * Modify the state of the specified workflow instance.
606
     * @param integer $id The workflow instance id.
607
     * @param integer $newState the new state to change the workflow instance to.
608
     *
609
     * @throws InvalidArgumentException
610
     * @throws InvalidEntryStateException
611
     * @throws InternalWorkflowException
612
     */
613 16
    public function changeEntryState($id, $newState)
614
    {
615 16
        $store = $this->getPersistence();
616 16
        $entry = $store->findEntry($id);
617
618 16
        if ($newState === $entry->getState()) {
619
            return;
620
        }
621
622 16
        if ($this->canModifyEntryState($id, $newState)) {
623 16
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
624
                $currentSteps = $this->getCurrentSteps($id);
625
626
                if (count($currentSteps) > 0) {
627
                    $entryEngine = $this->getEngineManager()->getEntryEngine();
628
                    $entryEngine->completeEntry(null, $id, $currentSteps, $newState);
629
                }
630
            }
631
632 16
            $store->setEntryState($id, $newState);
633 16
        } else {
634
            $errMsg = sprintf(
635
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
636
                $id,
637
                $entry->getState(),
638
                $newState
639
            );
640
641
            throw new InvalidEntryStateException($errMsg);
642
        }
643
644 16
        $msg = sprintf(
645 16
            '%s : Новое состояние: %s',
646 16
            $entry->getId(),
647 16
            $entry->getState()
648 16
        );
649 16
        $this->getLog()->debug($msg);
650 16
    }
651
652
653
654
655
656
    /**
657
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
658
     *
659
     * @param string $workflowName имя workflow
660
     * @param integer $initialAction id начального состояния
661
     * @param TransientVarsInterface $inputs
662
     *
663
     * @return bool
664
     *
665
     * @throws InvalidArgumentException
666
     * @throws WorkflowException
667
     * @throws InternalWorkflowException
668
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
669
     */
670
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
671
    {
672
        $mockWorkflowName = $workflowName;
673
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
674
675
        try {
676
            $ps = PropertySetManager::getInstance('memory', []);
677
            if (!$ps instanceof PropertySetInterface) {
678
                $errMsg = 'Invalid create PropertySet';
679
                throw new InternalWorkflowException($errMsg);
680
            }
681
        } catch (\Exception $e) {
682
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
683
        }
684
685
686
687
688
        if (null === $inputs) {
689
            $inputs = $this->transientVarsFactory();
690
        }
691
        $transientVars = $inputs;
692
693
        try {
694
            $this->getEngineManager()->getDataEngine()->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
695
696
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
697
698
            return $result;
699
        } catch (InvalidActionException $e) {
700
            $this->getLog()->error($e->getMessage(), [$e]);
701
702
            return false;
703
        } catch (WorkflowException $e) {
704
            $errMsg = sprintf(
705
                'Ошибка при проверки canInitialize: %s',
706
                $e->getMessage()
707
            );
708
            $this->getLog()->error($errMsg, [$e]);
709
710
            return false;
711
        }
712
    }
713
714
715
    /**
716
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
717
     *
718
     * @param string $workflowName имя workflow
719
     * @param integer $initialAction id начального состояния
720
     * @param TransientVarsInterface $transientVars
721
     *
722
     * @param PropertySetInterface $ps
723
     *
724
     * @return bool
725
     *
726
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
727
     * @throws InvalidActionException
728
     * @throws InternalWorkflowException
729
     * @throws WorkflowException
730
     */
731 19
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
732
    {
733 19
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
734
735 19
        $actionDescriptor = $wf->getInitialAction($initialAction);
736
737 19
        if (null === $actionDescriptor) {
738
            $errMsg = sprintf(
739
                'Некорректное инициирующие действие # %s',
740
                $initialAction
741
            );
742
            throw new InvalidActionException($errMsg);
743
        }
744
745 19
        $restriction = $actionDescriptor->getRestriction();
746
747
748 19
        $conditions = null;
749 19
        if (null !== $restriction) {
750 2
            $conditions = $restriction->getConditionsDescriptor();
751 2
        }
752
753 19
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
754 19
        $passesConditions = $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
755
756 19
        return $passesConditions;
757
    }
758
759
    /**
760
     * Возвращает резолвер
761
     *
762
     * @return TypeResolverInterface
763
     */
764 19
    public function getResolver()
765
    {
766 19
        if (null !== $this->typeResolver) {
767 16
            return $this->typeResolver;
768
        }
769
770 19
        $classResolver = $this->getDefaultTypeResolverClass();
771 19
        $r = new ReflectionClass($classResolver);
772 19
        $resolver = $r->newInstance();
773 19
        $this->typeResolver = $resolver;
774
775 19
        return $this->typeResolver;
776
    }
777
778
    /**
779
     * Возвращает хранилище состояния workflow
780
     *
781
     * @return WorkflowStoreInterface
782
     *
783
     * @throws InternalWorkflowException
784
     */
785 19
    protected function getPersistence()
786
    {
787 19
        return $this->getConfiguration()->getWorkflowStore();
788
    }
789
790
    /**
791
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
792
     * инициализирует ее.
793
     *
794
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
795
     *
796
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
797
     *
798
     * @throws InternalWorkflowException
799
     */
800 19
    public function getConfiguration()
801
    {
802 19
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
803
804 19
        if (!$config->isInitialized()) {
805
            try {
806
                $config->load(null);
807
            } catch (FactoryException $e) {
808
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
809
                $this->getLog()->critical($errMsg, ['exception' => $e]);
810
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
811
            }
812
        }
813
814 19
        return $config;
815
    }
816
817
    /**
818
     * @return LoggerInterface
819
     */
820 17
    public function getLog()
821
    {
822 17
        return $this->log;
823
    }
824
825
    /**
826
     * @param LoggerInterface $log
827
     *
828
     * @return $this
829
     *
830
     * @throws InternalWorkflowException
831
     */
832
    public function setLog($log)
833
    {
834
        try {
835
            LogFactory::validLogger($log);
836
        } catch (\Exception $e) {
837
            $errMsg = 'Ошибка при валидации логера';
838
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
839
        }
840
841
842
        $this->log = $log;
843
844
        return $this;
845
    }
846
847
848
    /**
849
     * Get the workflow descriptor for the specified workflow name.
850
     *
851
     * @param string $workflowName The workflow name.
852
     * @return WorkflowDescriptor
853
     *
854
     * @throws InternalWorkflowException
855
     */
856
    public function getWorkflowDescriptor($workflowName)
857
    {
858
        try {
859
            return $this->getConfiguration()->getWorkflow($workflowName);
860
        } catch (FactoryException $e) {
861
            $errMsg = 'Ошибка при загрузке workflow';
862
            $this->getLog()->error($errMsg, ['exception' => $e]);
863
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
864
        }
865
    }
866
867
868
    /**
869
     * Executes a special trigger-function using the context of the given workflow instance id.
870
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
871
     *
872
     * @param integer $id The workflow instance id
873
     * @param integer $triggerId The id of the special trigger-function
874
     *
875
     * @throws InvalidArgumentException
876
     * @throws WorkflowException
877
     * @throws InternalWorkflowException
878
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
879
     */
880
    public function executeTriggerFunction($id, $triggerId)
881
    {
882
        $store = $this->getPersistence();
883
        $entry = $store->findEntry($id);
884
885
        if (null === $entry) {
886
            $errMsg = sprintf(
887
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
888
                $triggerId,
889
                $id
890
            );
891
            $this->getLog()->warning($errMsg);
892
            return;
893
        }
894
895
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
896
897
        $ps = $store->getPropertySet($id);
898
        $transientVars = $this->transientVarsFactory();
899
900
        $this->getEngineManager()->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
901
902
        $functionsEngine = $this->getEngineManager()->getFunctionsEngine();
903
        $functionsEngine->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
904
    }
905
906
907
    /**
908
     * @param WorkflowDescriptor   $wf
909
     * @param StepInterface        $step
910
     * @param TransientVarsInterface                $transientVars
911
     * @param PropertySetInterface $ps
912
     *
913
     * @return array
914
     *
915
     * @throws InternalWorkflowException
916
     * @throws WorkflowException
917
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
918
     */
919
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
920
    {
921
        $l = [];
922
        $s = $wf->getStep($step->getStepId());
923
924
        if (null === $s) {
925
            $errMsg = sprintf(
926
                'getAvailableActionsForStep вызван с не существующим id шага %s',
927
                $step->getStepId()
928
            );
929
930
            $this->getLog()->warning($errMsg);
931
932
            return $l;
933
        }
934
935
        $actions  = $s->getActions();
936
937
        if (null === $actions || 0  === $actions->count()) {
938
            return $l;
939
        }
940
941
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
942
        foreach ($actions as $action) {
943
            $restriction = $action->getRestriction();
944
            $conditions = null;
945
946
            $transientVars['actionId'] = $action->getId();
947
948
949
            if (null !== $restriction) {
950
                $conditions = $restriction->getConditionsDescriptor();
951
            }
952
953
            $f = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
954
                 && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
955
            if ($f) {
956
                $l[] = $action->getId();
957
            }
958
        }
959
960
        return $l;
961
    }
962
963
    /**
964
     * @param ConfigurationInterface $configuration
965
     *
966
     * @return $this
967
     */
968 19
    public function setConfiguration(ConfigurationInterface $configuration)
969
    {
970 19
        $this->configuration = $configuration;
971
972 19
        return $this;
973
    }
974
975
    /**
976
     * Возвращает состояние для текущего экземпляра workflow
977
     *
978
     * @param integer $id id экземпляра workflow
979
     * @return integer id текущего состояния
980
     *
981
     * @throws InternalWorkflowException
982
     */
983
    public function getEntryState($id)
984
    {
985
        try {
986
            $store = $this->getPersistence();
987
988
            return $store->findEntry($id)->getState();
989
        } catch (StoreException $e) {
990
            $errMsg = sprintf(
991
                'Ошибка при получение состояния экземпляра workflow c id# %s',
992
                $id
993
            );
994
            $this->getLog()->error($errMsg, [$e]);
995
        }
996
997
        return WorkflowEntryInterface::UNKNOWN;
998
    }
999
1000
1001
    /**
1002
     * Настройки хранилища
1003
     *
1004
     * @return array
1005
     *
1006
     * @throws InternalWorkflowException
1007
     */
1008
    public function getPersistenceProperties()
1009
    {
1010
        return $this->getConfiguration()->getPersistenceArgs();
1011
    }
1012
1013
1014
    /**
1015
     * Get the PropertySet for the specified workflow instance id.
1016
     * @param integer $id The workflow instance id.
1017
     *
1018
     * @return PropertySetInterface
1019
     * @throws InternalWorkflowException
1020
     */
1021
    public function getPropertySet($id)
1022
    {
1023
        $ps = null;
1024
1025
        try {
1026
            $ps = $this->getPersistence()->getPropertySet($id);
1027
        } catch (StoreException $e) {
1028
            $errMsg = sprintf(
1029
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1030
                $id
1031
            );
1032
            $this->getLog()->error($errMsg, [$e]);
1033
        }
1034
1035
        return $ps;
1036
    }
1037
1038
    /**
1039
     * @return string[]
1040
     *
1041
     * @throws InternalWorkflowException
1042
     */
1043
    public function getWorkflowNames()
1044
    {
1045
        try {
1046
            return $this->getConfiguration()->getWorkflowNames();
1047
        } catch (FactoryException $e) {
1048
            $errMsg = 'Ошибка при получение имен workflow';
1049
            $this->getLog()->error($errMsg, [$e]);
1050
        }
1051
1052
        return [];
1053
    }
1054
1055
    /**
1056
     * @param TypeResolverInterface $typeResolver
1057
     *
1058
     * @return $this
1059
     */
1060
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1061
    {
1062
        $this->typeResolver = $typeResolver;
1063
1064
        return $this;
1065
    }
1066
1067
1068
    /**
1069
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1070
     * @param integer $id id the workflow instance id.
1071
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1072
     *
1073
     * @return array  A List of permissions specified currently (a permission is a string name).
1074
     *
1075
     */
1076
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1077
    {
1078
        try {
1079
            $store = $this->getPersistence();
1080
            $entry = $store->findEntry($id);
1081
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1082
1083
            $ps = $store->getPropertySet($id);
1084
1085
            if (null === $inputs) {
1086
                $inputs = $this->transientVarsFactory();
1087
            }
1088
            $transientVars = $inputs;
1089
1090
            $currentSteps = $store->findCurrentSteps($id);
1091
1092
            $engineManager = $this->getEngineManager();
1093
            try {
1094
                $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1095
            } catch (\Exception $e) {
1096
                $errMsg = sprintf(
1097
                    'Внутреннея ошибка: %s',
1098
                    $e->getMessage()
1099
                );
1100
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1101
            }
1102
1103
1104
            $s = [];
1105
1106
            $conditionsEngine = $engineManager->getConditionsEngine();
1107
            foreach ($currentSteps as $step) {
1108
                $stepId = $step->getStepId();
1109
1110
                $xmlStep = $wf->getStep($stepId);
1111
1112
                $securities = $xmlStep->getPermissions();
1113
1114
                foreach ($securities as $security) {
1115
                    if (!$security instanceof PermissionDescriptor) {
1116
                        $errMsg = 'Invalid PermissionDescriptor';
1117
                        throw new InternalWorkflowException($errMsg);
1118
                    }
1119
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1120
                    if (null !== $security->getRestriction() && $conditionsEngine->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1121
                        $s[$security->getName()] = $security->getName();
1122
                    }
1123
                }
1124
            }
1125
1126
            return $s;
1127
        } catch (\Exception $e) {
1128
            $errMsg = sprintf(
1129
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1130
                $id
1131
            );
1132
            $this->getLog()->error($errMsg, [$e]);
1133
        }
1134
1135
        return [];
1136
    }
1137
1138
1139
    /**
1140
     * Get the name of the specified workflow instance.
1141
     *
1142
     * @param integer $id the workflow instance id.
1143
     *
1144
     * @return string
1145
     *
1146
     * @throws InternalWorkflowException
1147
     */
1148
    public function getWorkflowName($id)
1149
    {
1150
        try {
1151
            $store = $this->getPersistence();
1152
            $entry = $store->findEntry($id);
1153
1154
            if (null !== $entry) {
1155
                return $entry->getWorkflowName();
1156
            }
1157
        } catch (FactoryException $e) {
1158
            $errMsg = sprintf(
1159
                'Ошибка при получение имен workflow для инстанса с id # %s',
1160
                $id
1161
            );
1162
            $this->getLog()->error($errMsg, [$e]);
1163
        }
1164
1165
        return null;
1166
    }
1167
1168
    /**
1169
     * Удаляет workflow
1170
     *
1171
     * @param string $workflowName
1172
     *
1173
     * @return bool
1174
     *
1175
     * @throws InternalWorkflowException
1176
     */
1177
    public function removeWorkflowDescriptor($workflowName)
1178
    {
1179
        return $this->getConfiguration()->removeWorkflow($workflowName);
1180
    }
1181
1182
    /**
1183
     * @param                    $workflowName
1184
     * @param WorkflowDescriptor $descriptor
1185
     * @param                    $replace
1186
     *
1187
     * @return bool
1188
     *
1189
     * @throws InternalWorkflowException
1190
     */
1191
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1192
    {
1193
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1194
1195
        return $success;
1196
    }
1197
1198
1199
    /**
1200
     * Query the workflow store for matching instances
1201
     *
1202
     * @param WorkflowExpressionQuery $query
1203
     *
1204
     * @return array
1205
     *
1206
     * @throws InternalWorkflowException
1207
     */
1208
    public function query(WorkflowExpressionQuery $query)
1209
    {
1210
        return $this->getPersistence()->query($query);
1211
    }
1212
1213
    /**
1214
     * @return string
1215
     */
1216 19
    public function getDefaultTypeResolverClass()
1217
    {
1218 19
        return $this->defaultTypeResolverClass;
1219
    }
1220
1221
    /**
1222
     * @param string $defaultTypeResolverClass
1223
     *
1224
     * @return $this
1225
     */
1226
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1227
    {
1228
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1229
1230
        return $this;
1231
    }
1232
1233
1234
    /**
1235
     * @return WorkflowContextInterface
1236
     */
1237 19
    public function getContext()
1238
    {
1239 19
        return $this->context;
1240
    }
1241
}
1242