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 ( 37e84b...c7bcbf )
by Андрей
06:51
created

AbstractWorkflow::createNewCurrentStep()   F

Complexity

Conditions 18
Paths 1765

Size

Total Lines 109
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 44
CRAP Score 37.0571

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 109
ccs 44
cts 72
cp 0.6111
rs 2
cc 18
eloc 72
nc 1765
nop 8
crap 37.0571

2 Methods

Rating   Name   Duplication   Size   Complexity  
A AbstractWorkflow::getWorkflowDescriptor() 0 10 2
B AbstractWorkflow::executeTriggerFunction() 0 25 2

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 Traversable;
32
use SplObjectStorage;
33
use OldTown\Workflow\TransientVars\BaseTransientVars;
34
use ReflectionClass;
35
use ArrayObject;
36
use OldTown\Workflow\Engine\EngineManagerInterface;
37
use OldTown\Workflow\Engine\EngineManager;
38
39
40
41
/**
42
 * Class AbstractWorkflow
43
 *
44
 * @package OldTown\Workflow
45
 */
46
abstract class  AbstractWorkflow implements WorkflowInterface
47
{
48
    /**
49
     * @var string
50
     */
51
    const CURRENT_STEPS = 'currentSteps';
52
53
    /**
54
     * @var string
55
     */
56
    const HISTORY_STEPS = 'historySteps';
57
58
    /**
59
     * @var WorkflowContextInterface
60
     */
61
    protected $context;
62
63
    /**
64
     * @var ConfigurationInterface
65
     */
66
    protected $configuration;
67
68
69
    /**
70
     * @var TypeResolverInterface
71
     */
72
    protected $typeResolver;
73
74
    /**
75
     * Логер
76
     *
77
     * @var LoggerInterface
78
     */
79
    protected $log;
80
81
    /**
82
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
83
     *
84
     * @var string
85
     */
86
    protected $defaultTypeResolverClass = TypeResolver::class;
87
88
    /**
89
     * Карта переходов состояния процесса workflow
90
     *
91
     * @var null|array
92
     */
93
    protected $mapEntryState;
94
95
    /**
96
     * Менеджер движков
97
     *
98
     * @var EngineManagerInterface
99
     */
100
    protected $engineManager;
101
102
    /**
103
     * AbstractWorkflow constructor.
104
     *
105
     * @throws InternalWorkflowException
106
     */
107 19
    public function __construct()
108
    {
109 19
        $this->initLoger();
110 19
        $this->initMapEntryState();
111 19
    }
112
113
    /**
114
     * Устанавливает менеджер движков
115
     *
116
     * @return EngineManagerInterface
117
     */
118 19
    public function getEngineManager()
119
    {
120 19
        if ($this->engineManager) {
121 19
            return $this->engineManager;
122
        }
123 19
        $this->engineManager = new EngineManager($this);
124
125 19
        return $this->engineManager;
126 2
    }
127
128
    /**
129
     * Возвращает менеджер движков
130
     *
131
     * @param EngineManagerInterface $engineManager
132
     *
133
     * @return $this
134
     */
135
    public function setEngineManager(EngineManagerInterface $engineManager)
136
    {
137
        $this->engineManager = $engineManager;
138
139
        return $this;
140
    }
141
142
    /**
143
     * Инициация карты переходов состояния процесса workflow
144
     */
145 19
    protected function initMapEntryState()
146
    {
147 19
        $this->mapEntryState = [
148 19
            WorkflowEntryInterface::COMPLETED => [
149 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
150 19
            ],
151 19
            WorkflowEntryInterface::CREATED => [],
152 19
            WorkflowEntryInterface::ACTIVATED => [
153 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED,
154 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
155
156 19
            ],
157 19
            WorkflowEntryInterface::SUSPENDED => [
158 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
159 19
            ],
160 19
            WorkflowEntryInterface::KILLED => [
161 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
162 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED,
163 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED
164 19
            ]
165 19
        ];
166 19
    }
167
168
    /**
169
     * Инициализация системы логирования
170
     *
171
     * @throws InternalWorkflowException
172
     */
173 19
    protected function initLoger()
174 1
    {
175
        try {
176 19
            $this->log = LogFactory::getLog();
177 19
        } catch (\Exception $e) {
178
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
179
        }
180 19
    }
181
182
    /**
183
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
184
     * Workflow может быть инициализированно только один раз
185
     *
186
     * @param string $workflowName Имя workflow
187
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
188
     * @param TransientVarsInterface $inputs Данные введеные пользователем
189
     *
190
     * @return integer
191
     *
192
     * @throws InternalWorkflowException
193
     * @throws InvalidActionException
194
     * @throws InvalidRoleException
195
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
196
     * @throws InvalidArgumentException
197
     * @throws WorkflowException
198
     */
199 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
200
    {
201
        try {
202 19
            $initialAction = (integer)$initialAction;
203
204 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
205
206 19
            $store = $this->getPersistence();
207
208 19
            $entry = $store->createEntry($workflowName);
209
210 19
            $ps = $store->getPropertySet($entry->getId());
211
212
213 19
            if (null === $inputs) {
214
                $inputs = $this->transientVarsFactory();
215
            }
216
217 19
            $engineManager = $this->getEngineManager();
218
219 19
            $transientVars = $inputs;
220 19
            $inputs = clone $transientVars;
221
222 19
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
223
224 19
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
225 1
                $this->context->setRollbackOnly();
226 1
                $errMsg = 'You are restricted from initializing this workflow';
227 1
                throw new InvalidRoleException($errMsg);
228
            }
229
230 18
            $action = $wf->getInitialAction($initialAction);
231
232 18
            if (null === $action) {
233
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
234
                throw new InvalidActionException($errMsg);
235
            }
236
237 18
            $currentSteps = new SplObjectStorage();
238 18
            $transitionManager = $engineManager->getTransitionEngine();
239 18
            $transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
240
241 16
            $entryId = $entry->getId();
242 19
        } catch (WorkflowException $e) {
243
            $this->context->setRollbackOnly();
244
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
245
        }
246
247 16
        return $entryId;
248
    }
249
250
251
    /**
252
     * Проверка того что данные могут быть использованы в цикле
253
     *
254
     * @param $data
255
     *
256
     * @throws InvalidArgumentException
257
     */
258
    protected function validateIterateData($data)
259
    {
260 View Code Duplication
        if (!is_array($data) && !$data  instanceof Traversable) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
261
            $errMsg = 'Data not iterate';
262
            throw new InvalidArgumentException($errMsg);
263
        }
264
    }
265
266
267
268
    /**
269
     * @param $id
270
     * @param $inputs
271
     *
272
     * @return array
273
     *
274
     */
275
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
276
    {
277
        try {
278
            $store = $this->getPersistence();
279
            $entry = $store->findEntry($id);
280
281
            if (null === $entry) {
282
                $errMsg = sprintf(
283
                    'Не существует экземпляра workflow c id %s',
284
                    $id
285
                );
286
                throw new InvalidArgumentException($errMsg);
287
            }
288
289
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
290
                return [];
291
            }
292
293
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
294
295
            $l = [];
296
            $ps = $store->getPropertySet($id);
297
298
            $transientVars = $inputs;
299
            if (null === $transientVars) {
300
                $transientVars = $this->transientVarsFactory();
301
            }
302
303
            $currentSteps = $store->findCurrentSteps($id);
304
305
            $engineManager =  $this->getEngineManager();
306
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
307
308
            $globalActions = $wf->getGlobalActions();
309
310
            $conditionsEngine = $engineManager->getConditionsEngine();
311
            foreach ($globalActions as $action) {
312
                $restriction = $action->getRestriction();
313
                $conditions = null;
314
315
                $transientVars['actionId'] = $action->getId();
316
317
                if (null !== $restriction) {
318
                    $conditions = $restriction->getConditionsDescriptor();
319
                }
320
321
                $flag = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
322
                if ($flag) {
323
                    $l[] = $action->getId();
324
                }
325
            }
326
327
            foreach ($currentSteps as $currentStep) {
328
                $availableActionsForStep = $this->getAvailableActionsForStep($wf, $currentStep, $transientVars, $ps);
329
                foreach ($availableActionsForStep as $actionId) {
330
                    $l[] = $actionId;
331
                }
332
            }
333
334
335
            return array_unique($l);
336
        } catch (\Exception $e) {
337
            $errMsg = 'Ошибка проверки доступных действий';
338
            $this->getLog()->error($errMsg, [$e]);
339
        }
340
341
        return [];
342
    }
343
344
    /**
345
     * Создает хранилище переменных
346
     *
347
     * @param $class
348
     *
349
     * @return TransientVarsInterface
350
     */
351
    protected function transientVarsFactory($class = BaseTransientVars::class)
352
    {
353
        $r = new \ReflectionClass($class);
354
        return $r->newInstance();
355
    }
356
357
    /**
358
     *
359
     *
360
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
361
     *
362
     * @param integer $entryId id запущенного процесса workflow
363
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
364
     * @param TransientVarsInterface $inputs Входные данные для перехода
365
     *
366
     * @return void
367
     *
368
     * @throws WorkflowException
369
     * @throws InvalidActionException
370
     * @throws InvalidArgumentException
371
     * @throws InternalWorkflowException
372
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
373
     */
374 8
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
375
    {
376 8
        $actionId = (integer)$actionId;
377 8
        if (null === $inputs) {
378
            $inputs = $this->transientVarsFactory();
379
        }
380 8
        $transientVars = $inputs;
381 8
        $inputs = clone $transientVars;
382
383 8
        $store = $this->getPersistence();
384 8
        $entry = $store->findEntry($entryId);
385
386 8
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
387
            return;
388
        }
389
390 8
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
391
392 8
        $currentSteps = $store->findCurrentSteps($entryId);
393 8
        if (!$currentSteps instanceof SplObjectStorage) {
394
            $errMsg = 'Invalid currentSteps';
395
            throw new InternalWorkflowException($errMsg);
396
        }
397
398 8
        $action = null;
399
400 8
        $ps = $store->getPropertySet($entryId);
401
402 8
        $engineManager = $this->getEngineManager();
403 8
        $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
404
405
406 8
        $validAction = false;
407
408 8
        $transitionEngine = $engineManager->getTransitionEngine();
409 8
        foreach ($wf->getGlobalActions() as $actionDesc) {
410 View Code Duplication
            if ($actionId === $actionDesc->getId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
411
                $action = $actionDesc;
412
413
                if ($transitionEngine->isActionAvailable($action, $transientVars, $ps, 0)) {
414
                    $validAction = true;
415
                }
416
            }
417 8
        }
418
419
420 8
        foreach ($currentSteps as $step) {
421 8
            $s = $wf->getStep($step->getStepId());
422
423 8
            foreach ($s->getActions() as $actionDesc) {
424 8
                if (!$actionDesc instanceof ActionDescriptor) {
425
                    $errMsg = 'Invalid action descriptor';
426
                    throw new InternalWorkflowException($errMsg);
427
                }
428
429 8 View Code Duplication
                if ($actionId === $actionDesc->getId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
430 8
                    $action = $actionDesc;
431
432 8
                    if ($transitionEngine->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
433 6
                        $validAction = true;
434 6
                    }
435 8
                }
436 8
            }
437 8
        }
438
439
440 8
        if (!$validAction) {
441 2
            $errMsg = sprintf(
442 2
                'Action %s is invalid',
443
                $actionId
444 2
            );
445 2
            throw new InvalidActionException($errMsg);
446
        }
447
448
449
        try {
450 6
            $transitionManager = $this->getEngineManager()->getTransitionEngine();
451 6
            if ($transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps)) {
452
                $this->checkImplicitFinish($action, $entryId);
453
            }
454 6
        } catch (WorkflowException $e) {
455
            $this->context->setRollbackOnly();
456
            /** @var  WorkflowException $e*/
457
            throw $e;
458
        }
459 5
    }
460
461
    /**
462
     * @param ActionDescriptor $action
463
     * @param                  $id
464
     *
465
     * @return void
466
     *
467
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
468
     * @throws InvalidArgumentException
469
     * @throws InternalWorkflowException
470
     */
471
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
472
    {
473
        $store = $this->getPersistence();
474
        $entry = $store->findEntry($id);
475
476
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
477
478
        $currentSteps = $store->findCurrentSteps($id);
479
480
        $isCompleted = $wf->getGlobalActions()->count() === 0;
481
482
        foreach ($currentSteps as $step) {
483
            if ($isCompleted) {
484
                break;
485
            }
486
487
            $stepDes = $wf->getStep($step->getStepId());
488
489
            if ($stepDes->getActions()->count() > 0) {
490
                $isCompleted = true;
491
            }
492
        }
493
494
        if ($isCompleted) {
495
            $entryEngine = $this->getEngineManager()->getEntryEngine();
496
            $entryEngine->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
497
        }
498
    }
499
500
501
502
    /**
503
     *
504
     * Check if the state of the specified workflow instance can be changed to the new specified one.
505
     *
506
     * @param integer $id The workflow instance id.
507
     * @param integer $newState The new state id.
508
     *
509
     * @return boolean true if the state of the workflow can be modified, false otherwise.
510
     *
511
     * @throws InternalWorkflowException
512
     */
513 16
    public function canModifyEntryState($id, $newState)
514
    {
515 16
        $store = $this->getPersistence();
516 16
        $entry = $store->findEntry($id);
517
518 16
        $currentState = $entry->getState();
519
520 16
        return array_key_exists($newState, $this->mapEntryState) && array_key_exists($currentState, $this->mapEntryState[$newState]);
521
    }
522
523
524
    /**
525
     *
526
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
527
     *
528
     * @param integer $entryId id экземпляра workflow
529
     *
530
     * @return SplObjectStorage|StepInterface[]
531
     *
532
     * @throws InternalWorkflowException
533
     */
534
    public function getCurrentSteps($entryId)
535
    {
536
        return $this->getStepFromStorage($entryId, static::CURRENT_STEPS);
537
    }
538
539
    /**
540
     * Возвращает информацию о том в какие шаги, были осуществленны переходы, для процесса workflow с заданным id
541
     *
542
     * @param integer $entryId уникальный идентификатор процесса workflow
543
     *
544
     * @return StepInterface[]|SplObjectStorage список шагов
545
     *
546
     * @throws InternalWorkflowException
547
     */
548
    public function getHistorySteps($entryId)
549
    {
550
        return $this->getStepFromStorage($entryId, static::HISTORY_STEPS);
551
    }
552
553
    /**
554
     * Получение шагов информации о шагах процесса workflow
555
     *
556
     * @param $entryId
557
     * @param $type
558
     *
559
     * @return Spi\StepInterface[]|SplObjectStorage
560
     *
561
     * @throws InternalWorkflowException
562
     */
563
    protected function getStepFromStorage($entryId, $type)
564
    {
565
        try {
566
            $store = $this->getPersistence();
567
568
            if (static::CURRENT_STEPS === $type) {
569
                return $store->findCurrentSteps($entryId);
570
            } elseif (static::HISTORY_STEPS === $type) {
571
                return $store->findHistorySteps($entryId);
572
            }
573
        } catch (StoreException $e) {
574
            $errMsg = sprintf(
575
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
576
                $entryId
577
            );
578
            $this->getLog()->error($errMsg, [$e]);
579
        }
580
581
        return new SplObjectStorage();
582
    }
583
584
585
586
    /**
587
     *
588
     *
589
     * Modify the state of the specified workflow instance.
590
     * @param integer $id The workflow instance id.
591
     * @param integer $newState the new state to change the workflow instance to.
592
     *
593
     * @throws InvalidArgumentException
594
     * @throws InvalidEntryStateException
595
     * @throws InternalWorkflowException
596
     */
597 16
    public function changeEntryState($id, $newState)
598
    {
599 16
        $store = $this->getPersistence();
600 16
        $entry = $store->findEntry($id);
601
602 16
        if ($newState === $entry->getState()) {
603
            return;
604
        }
605
606 16
        if ($this->canModifyEntryState($id, $newState)) {
607 16
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
608
                $currentSteps = $this->getCurrentSteps($id);
609
610
                if (count($currentSteps) > 0) {
611
                    $entryEngine = $this->getEngineManager()->getEntryEngine();
612
                    $entryEngine->completeEntry(null, $id, $currentSteps, $newState);
613
                }
614
            }
615
616 16
            $store->setEntryState($id, $newState);
617 16
        } else {
618
            $errMsg = sprintf(
619
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
620
                $id,
621
                $entry->getState(),
622
                $newState
623
            );
624
625
            throw new InvalidEntryStateException($errMsg);
626
        }
627
628 16
        $msg = sprintf(
629 16
            '%s : Новое состояние: %s',
630 16
            $entry->getId(),
631 16
            $entry->getState()
632 16
        );
633 16
        $this->getLog()->debug($msg);
634 16
    }
635
636
637
638
639
640
    /**
641
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
642
     *
643
     * @param string $workflowName имя workflow
644
     * @param integer $initialAction id начального состояния
645
     * @param TransientVarsInterface $inputs
646
     *
647
     * @return bool
648
     *
649
     * @throws InvalidArgumentException
650
     * @throws WorkflowException
651
     * @throws InternalWorkflowException
652
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
653
     */
654
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
655
    {
656
        $mockWorkflowName = $workflowName;
657
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
658
659
        try {
660
            $ps = PropertySetManager::getInstance('memory', []);
661
            if (!$ps instanceof PropertySetInterface) {
662
                $errMsg = 'Invalid create PropertySet';
663
                throw new InternalWorkflowException($errMsg);
664
            }
665
        } catch (\Exception $e) {
666
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
667
        }
668
669
670
671
672
        if (null === $inputs) {
673
            $inputs = $this->transientVarsFactory();
674
        }
675
        $transientVars = $inputs;
676
677
        try {
678
            $this->getEngineManager()->getDataEngine()->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
679
680
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
681
682
            return $result;
683
        } catch (InvalidActionException $e) {
684
            $this->getLog()->error($e->getMessage(), [$e]);
685
686
            return false;
687
        } catch (WorkflowException $e) {
688
            $errMsg = sprintf(
689
                'Ошибка при проверки canInitialize: %s',
690
                $e->getMessage()
691
            );
692
            $this->getLog()->error($errMsg, [$e]);
693
694
            return false;
695
        }
696
    }
697
698
699
    /**
700
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
701
     *
702
     * @param string $workflowName имя workflow
703
     * @param integer $initialAction id начального состояния
704
     * @param TransientVarsInterface $transientVars
705
     *
706
     * @param PropertySetInterface $ps
707
     *
708
     * @return bool
709
     *
710
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
711
     * @throws InvalidActionException
712
     * @throws InternalWorkflowException
713
     * @throws WorkflowException
714
     */
715 19
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
716
    {
717 19
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
718
719 19
        $actionDescriptor = $wf->getInitialAction($initialAction);
720
721 19
        if (null === $actionDescriptor) {
722
            $errMsg = sprintf(
723
                'Некорректное инициирующие действие # %s',
724
                $initialAction
725
            );
726
            throw new InvalidActionException($errMsg);
727
        }
728
729 19
        $restriction = $actionDescriptor->getRestriction();
730
731
732 19
        $conditions = null;
733 19
        if (null !== $restriction) {
734 2
            $conditions = $restriction->getConditionsDescriptor();
735 2
        }
736
737 19
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
738 19
        $passesConditions = $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
739
740 19
        return $passesConditions;
741
    }
742
743
    /**
744
     * Возвращает резолвер
745
     *
746
     * @return TypeResolverInterface
747
     */
748 19
    public function getResolver()
749
    {
750 19
        if (null !== $this->typeResolver) {
751 16
            return $this->typeResolver;
752
        }
753
754 19
        $classResolver = $this->getDefaultTypeResolverClass();
755 19
        $r = new ReflectionClass($classResolver);
756 19
        $resolver = $r->newInstance();
757 19
        $this->typeResolver = $resolver;
758
759 19
        return $this->typeResolver;
760
    }
761
762
    /**
763
     * Возвращает хранилище состояния workflow
764
     *
765
     * @return WorkflowStoreInterface
766
     *
767
     * @throws InternalWorkflowException
768
     */
769 19
    protected function getPersistence()
770
    {
771 19
        return $this->getConfiguration()->getWorkflowStore();
772
    }
773
774
    /**
775
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
776
     * инициализирует ее.
777
     *
778
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
779
     *
780
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
781
     *
782
     * @throws InternalWorkflowException
783
     */
784 19
    public function getConfiguration()
785
    {
786 19
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
787
788 19
        if (!$config->isInitialized()) {
789
            try {
790
                $config->load(null);
791
            } catch (FactoryException $e) {
792
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
793
                $this->getLog()->critical($errMsg, ['exception' => $e]);
794
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
795
            }
796
        }
797
798 19
        return $config;
799
    }
800
801
    /**
802
     * @return LoggerInterface
803
     */
804 17
    public function getLog()
805
    {
806 17
        return $this->log;
807
    }
808
809
    /**
810
     * @param LoggerInterface $log
811
     *
812
     * @return $this
813
     *
814
     * @throws InternalWorkflowException
815
     */
816
    public function setLog($log)
817
    {
818
        try {
819
            LogFactory::validLogger($log);
820
        } catch (\Exception $e) {
821
            $errMsg = 'Ошибка при валидации логера';
822
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
823
        }
824
825
826
        $this->log = $log;
827
828
        return $this;
829
    }
830
831
832
    /**
833
     * Get the workflow descriptor for the specified workflow name.
834
     *
835
     * @param string $workflowName The workflow name.
836
     * @return WorkflowDescriptor
837
     *
838
     * @throws InternalWorkflowException
839
     */
840
    public function getWorkflowDescriptor($workflowName)
841
    {
842
        try {
843
            return $this->getConfiguration()->getWorkflow($workflowName);
844
        } catch (FactoryException $e) {
845
            $errMsg = 'Ошибка при загрузке workflow';
846
            $this->getLog()->error($errMsg, ['exception' => $e]);
847
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
848
        }
849
    }
850
851
852
    /**
853
     * Executes a special trigger-function using the context of the given workflow instance id.
854
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
855
     *
856
     * @param integer $id The workflow instance id
857
     * @param integer $triggerId The id of the special trigger-function
858
     *
859
     * @throws InvalidArgumentException
860
     * @throws WorkflowException
861
     * @throws InternalWorkflowException
862
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
863
     */
864
    public function executeTriggerFunction($id, $triggerId)
865
    {
866
        $store = $this->getPersistence();
867
        $entry = $store->findEntry($id);
868
869
        if (null === $entry) {
870
            $errMsg = sprintf(
871
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
872
                $triggerId,
873
                $id
874
            );
875
            $this->getLog()->warning($errMsg);
876
            return;
877
        }
878
879
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
880
881
        $ps = $store->getPropertySet($id);
882
        $transientVars = $this->transientVarsFactory();
883
884
        $this->getEngineManager()->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
885
886
        $functionsEngine = $this->getEngineManager()->getFunctionsEngine();
887
        $functionsEngine->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
888
    }
889
890
891
    /**
892
     * @param WorkflowDescriptor   $wf
893
     * @param StepInterface        $step
894
     * @param TransientVarsInterface                $transientVars
895
     * @param PropertySetInterface $ps
896
     *
897
     * @return array
898
     *
899
     * @throws InternalWorkflowException
900
     * @throws WorkflowException
901
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
902
     */
903
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
904
    {
905
        $l = [];
906
        $s = $wf->getStep($step->getStepId());
907
908
        if (null === $s) {
909
            $errMsg = sprintf(
910
                'getAvailableActionsForStep вызван с не существующим id шага %s',
911
                $step->getStepId()
912
            );
913
914
            $this->getLog()->warning($errMsg);
915
916
            return $l;
917
        }
918
919
        $actions  = $s->getActions();
920
921
        if (null === $actions || 0  === $actions->count()) {
922
            return $l;
923
        }
924
925
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
926
        foreach ($actions as $action) {
927
            $restriction = $action->getRestriction();
928
            $conditions = null;
929
930
            $transientVars['actionId'] = $action->getId();
931
932
933
            if (null !== $restriction) {
934
                $conditions = $restriction->getConditionsDescriptor();
935
            }
936
937
            $f = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
938
                 && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
939
            if ($f) {
940
                $l[] = $action->getId();
941
            }
942
        }
943
944
        return $l;
945
    }
946
947
    /**
948
     * @param ConfigurationInterface $configuration
949
     *
950
     * @return $this
951
     */
952 19
    public function setConfiguration(ConfigurationInterface $configuration)
953
    {
954 19
        $this->configuration = $configuration;
955
956 19
        return $this;
957
    }
958
959
    /**
960
     * Возвращает состояние для текущего экземпляра workflow
961
     *
962
     * @param integer $id id экземпляра workflow
963
     * @return integer id текущего состояния
964
     *
965
     * @throws InternalWorkflowException
966
     */
967
    public function getEntryState($id)
968
    {
969
        try {
970
            $store = $this->getPersistence();
971
972
            return $store->findEntry($id)->getState();
973
        } catch (StoreException $e) {
974
            $errMsg = sprintf(
975
                'Ошибка при получение состояния экземпляра workflow c id# %s',
976
                $id
977
            );
978
            $this->getLog()->error($errMsg, [$e]);
979
        }
980
981
        return WorkflowEntryInterface::UNKNOWN;
982
    }
983
984
985
    /**
986
     * Настройки хранилища
987
     *
988
     * @return array
989
     *
990
     * @throws InternalWorkflowException
991
     */
992
    public function getPersistenceProperties()
993
    {
994
        return $this->getConfiguration()->getPersistenceArgs();
995
    }
996
997
998
    /**
999
     * Get the PropertySet for the specified workflow instance id.
1000
     * @param integer $id The workflow instance id.
1001
     *
1002
     * @return PropertySetInterface
1003
     * @throws InternalWorkflowException
1004
     */
1005
    public function getPropertySet($id)
1006
    {
1007
        $ps = null;
1008
1009
        try {
1010
            $ps = $this->getPersistence()->getPropertySet($id);
1011
        } catch (StoreException $e) {
1012
            $errMsg = sprintf(
1013
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1014
                $id
1015
            );
1016
            $this->getLog()->error($errMsg, [$e]);
1017
        }
1018
1019
        return $ps;
1020
    }
1021
1022
    /**
1023
     * @return string[]
1024
     *
1025
     * @throws InternalWorkflowException
1026
     */
1027
    public function getWorkflowNames()
1028
    {
1029
        try {
1030
            return $this->getConfiguration()->getWorkflowNames();
1031
        } catch (FactoryException $e) {
1032
            $errMsg = 'Ошибка при получение имен workflow';
1033
            $this->getLog()->error($errMsg, [$e]);
1034
        }
1035
1036
        return [];
1037
    }
1038
1039
    /**
1040
     * @param TypeResolverInterface $typeResolver
1041
     *
1042
     * @return $this
1043
     */
1044
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1045
    {
1046
        $this->typeResolver = $typeResolver;
1047
1048
        return $this;
1049
    }
1050
1051
1052
    /**
1053
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1054
     * @param integer $id id the workflow instance id.
1055
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1056
     *
1057
     * @return array  A List of permissions specified currently (a permission is a string name).
1058
     *
1059
     */
1060
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1061
    {
1062
        try {
1063
            $store = $this->getPersistence();
1064
            $entry = $store->findEntry($id);
1065
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1066
1067
            $ps = $store->getPropertySet($id);
1068
1069
            if (null === $inputs) {
1070
                $inputs = $this->transientVarsFactory();
1071
            }
1072
            $transientVars = $inputs;
1073
1074
            $currentSteps = $store->findCurrentSteps($id);
1075
1076
            $engineManager = $this->getEngineManager();
1077
            try {
1078
                $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1079
            } catch (\Exception $e) {
1080
                $errMsg = sprintf(
1081
                    'Внутреннея ошибка: %s',
1082
                    $e->getMessage()
1083
                );
1084
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1085
            }
1086
1087
1088
            $s = [];
1089
1090
            $conditionsEngine = $engineManager->getConditionsEngine();
1091
            foreach ($currentSteps as $step) {
1092
                $stepId = $step->getStepId();
1093
1094
                $xmlStep = $wf->getStep($stepId);
1095
1096
                $securities = $xmlStep->getPermissions();
1097
1098
                foreach ($securities as $security) {
1099
                    if (!$security instanceof PermissionDescriptor) {
1100
                        $errMsg = 'Invalid PermissionDescriptor';
1101
                        throw new InternalWorkflowException($errMsg);
1102
                    }
1103
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1104
                    if (null !== $security->getRestriction() && $conditionsEngine->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1105
                        $s[$security->getName()] = $security->getName();
1106
                    }
1107
                }
1108
            }
1109
1110
            return $s;
1111
        } catch (\Exception $e) {
1112
            $errMsg = sprintf(
1113
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1114
                $id
1115
            );
1116
            $this->getLog()->error($errMsg, [$e]);
1117
        }
1118
1119
        return [];
1120
    }
1121
1122
1123
    /**
1124
     * Get the name of the specified workflow instance.
1125
     *
1126
     * @param integer $id the workflow instance id.
1127
     *
1128
     * @return string
1129
     *
1130
     * @throws InternalWorkflowException
1131
     */
1132
    public function getWorkflowName($id)
1133
    {
1134
        try {
1135
            $store = $this->getPersistence();
1136
            $entry = $store->findEntry($id);
1137
1138
            if (null !== $entry) {
1139
                return $entry->getWorkflowName();
1140
            }
1141
        } catch (FactoryException $e) {
1142
            $errMsg = sprintf(
1143
                'Ошибка при получение имен workflow для инстанса с id # %s',
1144
                $id
1145
            );
1146
            $this->getLog()->error($errMsg, [$e]);
1147
        }
1148
1149
        return null;
1150
    }
1151
1152
    /**
1153
     * Удаляет workflow
1154
     *
1155
     * @param string $workflowName
1156
     *
1157
     * @return bool
1158
     *
1159
     * @throws InternalWorkflowException
1160
     */
1161
    public function removeWorkflowDescriptor($workflowName)
1162
    {
1163
        return $this->getConfiguration()->removeWorkflow($workflowName);
1164
    }
1165
1166
    /**
1167
     * @param                    $workflowName
1168
     * @param WorkflowDescriptor $descriptor
1169
     * @param                    $replace
1170
     *
1171
     * @return bool
1172
     *
1173
     * @throws InternalWorkflowException
1174
     */
1175
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1176
    {
1177
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1178
1179
        return $success;
1180
    }
1181
1182
1183
    /**
1184
     * Query the workflow store for matching instances
1185
     *
1186
     * @param WorkflowExpressionQuery $query
1187
     *
1188
     * @return array
1189
     *
1190
     * @throws InternalWorkflowException
1191
     */
1192
    public function query(WorkflowExpressionQuery $query)
1193
    {
1194
        return $this->getPersistence()->query($query);
1195
    }
1196
1197
    /**
1198
     * @return string
1199
     */
1200 19
    public function getDefaultTypeResolverClass()
1201
    {
1202 19
        return $this->defaultTypeResolverClass;
1203
    }
1204
1205
    /**
1206
     * @param string $defaultTypeResolverClass
1207
     *
1208
     * @return $this
1209
     */
1210
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1211
    {
1212
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1213
1214
        return $this;
1215
    }
1216
1217
1218
    /**
1219
     * @return WorkflowContextInterface
1220
     */
1221 19
    public function getContext()
1222
    {
1223 19
        return $this->context;
1224
    }
1225
}
1226