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 ( bdde9b...50922e )
by Андрей
05:52
created

AbstractWorkflow::getStepFromStorage()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
rs 9.2
cc 4
eloc 13
nc 6
nop 2
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\InvalidInputException;
19
use OldTown\Workflow\Exception\InvalidRoleException;
20
use OldTown\Workflow\Exception\StoreException;
21
use OldTown\Workflow\Exception\WorkflowException;
22
use OldTown\Workflow\Loader\ActionDescriptor;
23
use OldTown\Workflow\Loader\ConditionDescriptor;
24
use OldTown\Workflow\Loader\ConditionsDescriptor;
25
use OldTown\Workflow\Loader\FunctionDescriptor;
26
use OldTown\Workflow\Loader\PermissionDescriptor;
27
use OldTown\Workflow\Loader\RegisterDescriptor;
28
use OldTown\Workflow\Loader\ResultDescriptor;
29
use OldTown\Workflow\Loader\ValidatorDescriptor;
30
use OldTown\Workflow\Loader\WorkflowDescriptor;
31
use OldTown\Workflow\Query\WorkflowExpressionQuery;
32
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
33
use OldTown\Workflow\Spi\StepInterface;
34
use OldTown\Workflow\Spi\WorkflowEntryInterface;
35
use OldTown\Workflow\Spi\WorkflowStoreInterface;
36
use OldTown\Workflow\TransientVars\TransientVarsInterface;
37
use Psr\Log\LoggerInterface;
38
use Traversable;
39
use SplObjectStorage;
40
use DateTime;
41
use OldTown\Workflow\TransientVars\BaseTransientVars;
42
use ReflectionClass;
43
use ArrayObject;
44
45
46
/**
47
 * Class AbstractWorkflow
48
 *
49
 * @package OldTown\Workflow
50
 */
51
abstract class  AbstractWorkflow implements WorkflowInterface
52
{
53
    /**
54
     * @var string
55
     */
56
    const CURRENT_STEPS = 'currentSteps';
57
58
    /**
59
     * @var string
60
     */
61
    const HISTORY_STEPS = 'historySteps';
62
63
    /**
64
     * @var WorkflowContextInterface
65
     */
66
    protected $context;
67
68
    /**
69
     * @var ConfigurationInterface
70
     */
71
    protected $configuration;
72
73
    /**
74
     *
75
     * @var array
76
     */
77
    protected $stateCache = [];
78
79
    /**
80
     * @var TypeResolverInterface
81
     */
82
    protected $typeResolver;
83
84
    /**
85
     * Логер
86
     *
87
     * @var LoggerInterface
88
     */
89
    protected $log;
90
91
    /**
92
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
93
     *
94
     * @var string
95
     */
96
    protected $defaultTypeResolverClass = TypeResolver::class;
97
98
    /**
99
     * AbstractWorkflow constructor.
100
     *
101
     * @throws InternalWorkflowException
102
     */
103
    public function __construct()
104
    {
105
        $this->initLoger();
106
    }
107
108
    /**
109
     * Инициализация системы логирования
110
     *
111
     * @throws InternalWorkflowException
112
     */
113
    protected function initLoger()
114
    {
115
        try {
116
            $this->log = LogFactory::getLog();
117
        } catch (\Exception $e) {
118
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
119
        }
120
    }
121
122
    /**
123
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
124
     * Workflow может быть инициализированно только один раз
125
     *
126
     * @param string $workflowName Имя workflow
127
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
128
     * @param TransientVarsInterface $inputs Данные введеные пользователем
129
     *
130
     * @return integer
131
     *
132
     * @throws InternalWorkflowException
133
     * @throws InvalidActionException
134
     * @throws InvalidRoleException
135
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
136
     * @throws InvalidArgumentException
137
     * @throws WorkflowException
138
     */
139
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
140
    {
141
        try {
142
            $initialAction = (integer)$initialAction;
143
144
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
145
146
            $store = $this->getPersistence();
147
148
            $entry = $store->createEntry($workflowName);
149
150
            $ps = $store->getPropertySet($entry->getId());
151
152
153
            if (null === $inputs) {
154
                $inputs = $this->transientVarsFactory();
155
            }
156
            $transientVars = $inputs;
157
            $inputs = clone $transientVars;
158
159
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
160
161
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
162
                $this->context->setRollbackOnly();
163
                $errMsg = 'You are restricted from initializing this workflow';
164
                throw new InvalidRoleException($errMsg);
165
            }
166
167
            $action = $wf->getInitialAction($initialAction);
168
169
            if (null === $action) {
170
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
171
                throw new InvalidActionException($errMsg);
172
            }
173
174
            $currentSteps = new SplObjectStorage();
175
            $this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
176
177
            $entryId = $entry->getId();
178
        } catch (WorkflowException $e) {
179
            $this->context->setRollbackOnly();
180
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
181
        }
182
183
184
        // now clone the memory PS to the real PS
185
        //PropertySetManager.clone(ps, store.getPropertySet(entryId));
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
186
        return $entryId;
187
    }
188
189
    /**
190
     * @param WorkflowEntryInterface $entry
191
     * @param TransientVarsInterface $transientVars
192
     * @param array|Traversable|RegisterDescriptor[]|SplObjectStorage $registersStorage
193
     * @param integer $actionId
194
     * @param array|Traversable $currentSteps
195
     * @param PropertySetInterface $ps
196
     *
197
     *
198
     * @return TransientVarsInterface
199
     *
200
     * @throws InvalidArgumentException
201
     * @throws WorkflowException
202
     * @throws InternalWorkflowException
203
     */
204
    protected function populateTransientMap(WorkflowEntryInterface $entry, TransientVarsInterface $transientVars, $registersStorage, $actionId = null, $currentSteps, PropertySetInterface $ps)
205
    {
206 View Code Duplication
        if (!is_array($currentSteps) && !$currentSteps  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...
207
            $errMsg = 'CurrentSteps должен быть массивом, либо реализовывать интерфейс Traversable';
208
            throw new InvalidArgumentException($errMsg);
209
        }
210
211 View Code Duplication
        if ($registersStorage 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...
212
            $registers = [];
213
            foreach ($registersStorage as $k => $v) {
214
                $registers[$k] = $v;
215
            }
216
        } elseif (is_array($registersStorage)) {
217
            $registers = $registersStorage;
218
        } else {
219
            $errMsg = 'Registers должен быть массивом, либо реализовывать интерфейс Traversable';
220
            throw new InvalidArgumentException($errMsg);
221
        }
222
        /** @var RegisterDescriptor[] $registers */
223
224
        $transientVars['context'] = $this->context;
225
        $transientVars['entry'] = $entry;
226
        $transientVars['entryId'] = $entry->getId();
227
        $transientVars['store'] = $this->getPersistence();
228
        $transientVars['configuration'] = $this->getConfiguration();
229
        $transientVars['descriptor'] = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
230
231
        if (null !== $actionId) {
232
            $transientVars['actionId'] = $actionId;
233
        }
234
235
        $transientVars['currentSteps'] = $currentSteps;
236
237
238
        foreach ($registers as $register) {
239
            $args = $register->getArgs();
240
            $type = $register->getType();
241
242
            try {
243
                $r = $this->getResolver()->getRegister($type, $args);
244
            } catch (\Exception $e) {
245
                $errMsg = 'Ошибка при инициализации register';
246
                $this->context->setRollbackOnly();
247
                throw new WorkflowException($errMsg, $e->getCode(), $e);
248
            }
249
250
            $variableName = $register->getVariableName();
251
            try {
252
                $value = $r->registerVariable($this->context, $entry, $args, $ps);
253
254
                $transientVars[$variableName] = $value;
255
            } catch (\Exception $e) {
256
                $this->context->setRollbackOnly();
257
258
                $errMsg = sprintf(
259
                    'При получение значения переменной %s из registry %s произошла ошибка',
260
                    $variableName,
261
                    get_class($r)
262
                );
263
264
                throw new WorkflowException($errMsg, $e->getCode(), $e);
265
            }
266
        }
267
268
        return $transientVars;
269
    }
270
271
    /**
272
     * Переход между двумя статусами
273
     *
274
     * @param WorkflowEntryInterface $entry
275
     * @param SplObjectStorage|StepInterface[] $currentSteps
276
     * @param WorkflowStoreInterface $store
277
     * @param WorkflowDescriptor $wf
278
     * @param ActionDescriptor $action
279
     * @param TransientVarsInterface $transientVars
280
     * @param TransientVarsInterface $inputs
281
     * @param PropertySetInterface $ps
282
     *
283
     * @return boolean
284
     *
285
     * @throws InternalWorkflowException
286
     */
287
    protected function transitionWorkflow(WorkflowEntryInterface $entry, SplObjectStorage $currentSteps, WorkflowStoreInterface $store, WorkflowDescriptor $wf, ActionDescriptor $action, TransientVarsInterface $transientVars, TransientVarsInterface $inputs, PropertySetInterface $ps)
288
    {
289
        try {
290
            $step = $this->getCurrentStep($wf, $action->getId(), $currentSteps, $transientVars, $ps);
291
292
            $validators = $action->getValidators();
293
            if ($validators->count() > 0) {
294
                $this->verifyInputs($entry, $validators, $transientVars, $ps);
295
            }
296
297
298
            if (null !== $step) {
299
                $stepPostFunctions = $wf->getStep($step->getStepId())->getPostFunctions();
300
                foreach ($stepPostFunctions as $function) {
301
                    $this->executeFunction($function, $transientVars, $ps);
302
                }
303
            }
304
305
            $preFunctions = $action->getPreFunctions();
306
            foreach ($preFunctions as $preFunction) {
307
                $this->executeFunction($preFunction, $transientVars, $ps);
308
            }
309
310
            $conditionalResults = $action->getConditionalResults();
311
            $extraPreFunctions = null;
312
            $extraPostFunctions = null;
313
314
            $theResult = null;
315
316
317
            $currentStepId = null !== $step ? $step->getStepId()  : -1;
318
            foreach ($conditionalResults as $conditionalResult) {
319
                if ($this->passesConditionsWithType(null, $conditionalResult->getConditions(), $transientVars, $ps, $currentStepId)) {
320
                    $theResult = $conditionalResult;
321
322
                    $validatorsStorage = $conditionalResult->getValidators();
323
                    if ($validatorsStorage->count() > 0) {
324
                        $this->verifyInputs($entry, $validatorsStorage, $transientVars, $ps);
325
                    }
326
327
                    $extraPreFunctions = $conditionalResult->getPreFunctions();
328
                    $extraPostFunctions = $conditionalResult->getPostFunctions();
329
330
                    break;
331
                }
332
            }
333
334
335
            if (null ===  $theResult) {
336
                $theResult = $action->getUnconditionalResult();
337
                $this->verifyInputs($entry, $theResult->getValidators(), $transientVars, $ps);
338
                $extraPreFunctions = $theResult->getPreFunctions();
339
                $extraPostFunctions = $theResult->getPostFunctions();
340
            }
341
342
            $logMsg = sprintf('theResult=%s %s', $theResult->getStep(), $theResult->getStatus());
343
            $this->getLog()->debug($logMsg);
344
345
346
            if ($extraPreFunctions && $extraPreFunctions->count() > 0) {
347
                foreach ($extraPreFunctions as $function) {
348
                    $this->executeFunction($function, $transientVars, $ps);
349
                }
350
            }
351
352
            $split = $theResult->getSplit();
353
            $join = $theResult->getJoin();
354
            if (null !== $split && 0 !== $split) {
355
                $splitDesc = $wf->getSplit($split);
356
                $results = $splitDesc->getResults();
357
                $splitPreFunctions = [];
358
                $splitPostFunctions = [];
359
360
                foreach ($results as $resultDescriptor) {
361
                    if ($resultDescriptor->getValidators()->count() > 0) {
362
                        $this->verifyInputs($entry, $resultDescriptor->getValidators(), $transientVars, $ps);
363
                    }
364
365
                    foreach ($resultDescriptor->getPreFunctions() as $function) {
366
                        $splitPreFunctions[] = $function;
367
                    }
368
                    foreach ($resultDescriptor->getPostFunctions() as $function) {
369
                        $splitPostFunctions[] = $function;
370
                    }
371
                }
372
373
                foreach ($splitPreFunctions as $function) {
374
                    $this->executeFunction($function, $transientVars, $ps);
375
                }
376
377
                if (!$action->isFinish()) {
378
                    $moveFirst = true;
379
380
                    //???????????????????
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
381
//                $theResults = [];
382
//                foreach ($results as $result) {
383
//                    $theResults[] = $result;
384
//                }
385
386
                    foreach ($results as $resultDescriptor) {
387
                        $moveToHistoryStep = null;
388
389
                        if ($moveFirst) {
390
                            $moveToHistoryStep = $step;
391
                        }
392
393
                        $previousIds = [];
394
395
                        if (null !== $step) {
396
                            $previousIds[] = $step->getStepId();
397
                        }
398
399
                        $this->createNewCurrentStep($resultDescriptor, $entry, $store, $action->getId(), $moveToHistoryStep, $previousIds, $transientVars, $ps);
400
                        $moveFirst = false;
401
                    }
402
                }
403
404
405
                foreach ($splitPostFunctions as $function) {
406
                    $this->executeFunction($function, $transientVars, $ps);
407
                }
408
            } elseif (null !== $join && 0 !== $join) {
409
                $joinDesc = $wf->getJoin($join);
410
                $oldStatus = $theResult->getOldStatus();
411
                $caller = $this->context->getCaller();
412
                if (null !== $step) {
413
                    $step = $store->markFinished($step, $action->getId(), new DateTime(), $oldStatus, $caller);
414
                } else {
415
                    $errMsg = 'Invalid step';
416
                    throw new InternalWorkflowException($errMsg);
417
                }
418
419
420
                $store->moveToHistory($step);
421
422
                /** @var StepInterface[] $joinSteps */
423
                $joinSteps = [];
424
                $joinSteps[] = $step;
425
426 View Code Duplication
                foreach ($currentSteps as $currentStep) {
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...
427
                    if ($currentStep->getId() !== $step->getId()) {
428
                        $stepDesc = $wf->getStep($currentStep->getStepId());
429
430
                        if ($stepDesc->resultsInJoin($join)) {
431
                            $joinSteps[] = $currentStep;
432
                        }
433
                    }
434
                }
435
436
                $historySteps = $store->findHistorySteps($entry->getId());
437
438 View Code Duplication
                foreach ($historySteps as $historyStep) {
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...
439
                    if ($historyStep->getId() !== $step->getId()) {
440
                        $stepDesc = $wf->getStep($historyStep->getStepId());
441
442
                        if ($stepDesc->resultsInJoin($join)) {
443
                            $joinSteps[] = $currentSteps;
444
                        }
445
                    }
446
                }
447
448
                $jn = new JoinNodes($joinSteps);
449
                $transientVars['jn'] = $jn;
450
451
452
                if ($this->passesConditionsWithType(null, $joinDesc->getConditions(), $transientVars, $ps, 0)) {
453
                    $joinResult = $joinDesc->getResult();
454
455
                    $joinResultValidators = $joinResult->getValidators();
456
                    if ($joinResultValidators->count() > 0) {
457
                        $this->verifyInputs($entry, $joinResultValidators, $transientVars, $ps);
458
                    }
459
460
                    foreach ($joinResult->getPreFunctions() as $function) {
461
                        $this->executeFunction($function, $transientVars, $ps);
462
                    }
463
464
                    $previousIds = [];
465
                    $i = 1;
466
467
                    foreach ($joinSteps as  $currentJoinStep) {
468
                        if (!$historySteps->contains($currentJoinStep) && $currentJoinStep->getId() !== $step->getId()) {
469
                            $store->moveToHistory($step);
470
                        }
471
472
                        $previousIds[$i] = $currentJoinStep->getId();
473
                    }
474
475
                    if (!$action->isFinish()) {
476
                        $previousIds[0] = $step->getId();
477
                        $theResult = $joinDesc->getResult();
478
479
                        $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), null, $previousIds, $transientVars, $ps);
480
                    }
481
482
                    foreach ($joinResult->getPostFunctions() as $function) {
483
                        $this->executeFunction($function, $transientVars, $ps);
484
                    }
485
                }
486
            } else {
487
                $previousIds = [];
488
489
                if (null !== $step) {
490
                    $previousIds[] = $step->getId();
491
                }
492
493
                if (!$action->isFinish()) {
494
                    $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), $step, $previousIds, $transientVars, $ps);
495
                }
496
            }
497
498
            if ($extraPostFunctions && $extraPostFunctions->count() > 0) {
499
                foreach ($extraPostFunctions as $function) {
500
                    $this->executeFunction($function, $transientVars, $ps);
501
                }
502
            }
503
504
            if (WorkflowEntryInterface::COMPLETED !== $entry->getState() && null !== $wf->getInitialAction($action->getId())) {
505
                $this->changeEntryState($entry->getId(), WorkflowEntryInterface::ACTIVATED);
506
            }
507
508
            if ($action->isFinish()) {
509
                $this->completeEntry($action, $entry->getId(), $this->getCurrentSteps($entry->getId()), WorkflowEntryInterface::COMPLETED);
510
                return true;
511
            }
512
513
            $availableAutoActions = $this->getAvailableAutoActions($entry->getId(), $inputs);
514
515
            if (count($availableAutoActions) > 0) {
516
                $this->doAction($entry->getId(), $availableAutoActions[0], $inputs);
517
            }
518
519
            return false;
520
        } catch (\Exception $e) {
521
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
522
        }
523
    }
524
525
526
    /**
527
     * @param       $id
528
     * @param TransientVarsInterface $inputs
529
     *
530
     * @return array
531
     */
532
    protected function getAvailableAutoActions($id, TransientVarsInterface $inputs)
533
    {
534
        try {
535
            $store = $this->getPersistence();
536
            $entry = $store->findEntry($id);
537
538
            if (null === $entry) {
539
                $errMsg = sprintf(
540
                    'Нет сущности workflow c id %s',
541
                    $id
542
                );
543
                throw new InvalidArgumentException($errMsg);
544
            }
545
546
547
            if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
548
                $logMsg = sprintf('--> состояние %s', $entry->getState());
549
                $this->getLog()->debug($logMsg);
550
                return [0];
551
            }
552
553
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
554
555 View Code Duplication
            if (null === $wf) {
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...
556
                $errMsg = sprintf(
557
                    'Нет workflow c именем %s',
558
                    $entry->getWorkflowName()
559
                );
560
                throw new InvalidArgumentException($errMsg);
561
            }
562
563
            $l = [];
564
            $ps = $store->getPropertySet($id);
565
            $transientVars = $inputs;
566
            $currentSteps = $store->findCurrentSteps($id);
567
568
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
569
570
            $globalActions = $wf->getGlobalActions();
571
572 View Code Duplication
            foreach ($globalActions as $action) {
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...
573
                $transientVars['actionId'] = $action->getId();
574
575
                if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
576
                    $l[] = $action->getId();
577
                }
578
            }
579
580 View Code Duplication
            foreach ($currentSteps as $step) {
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...
581
                $availableAutoActionsForStep = $this->getAvailableAutoActionsForStep($wf, $step, $transientVars, $ps);
582
                foreach ($availableAutoActionsForStep as $v) {
583
                    $l[] = $v;
584
                }
585
                //$l = array_merge($l, $availableAutoActionsForStep);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
586
            }
587
588
            $l = array_unique($l);
589
590
            return $l;
591
        } catch (\Exception $e) {
592
            $errMsg = 'Ошибка при проверке доступных действий';
593
            $this->getLog()->error($errMsg, [$e]);
594
        }
595
596
        return [];
597
    }
598
599
600
    /**
601
     * @param WorkflowDescriptor   $wf
602
     * @param StepInterface        $step
603
     * @param TransientVarsInterface                $transientVars
604
     * @param PropertySetInterface $ps
605
     *
606
     * @return array
607
     *
608
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
609
     * @throws InternalWorkflowException
610
     * @throws WorkflowException
611
     */
612
    protected function getAvailableAutoActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
613
    {
614
        $l = [];
615
        $s = $wf->getStep($step->getStepId());
616
617
        if (null === $s) {
618
            $msg = sprintf('getAvailableAutoActionsForStep вызвана с несуществующим id %s', $step->getStepId());
619
            $this->getLog()->debug($msg);
620
            return $l;
621
        }
622
623
624
        $actions = $s->getActions();
625
        if (null === $actions || 0 === $actions->count()) {
626
            return $l;
627
        }
628
629 View Code Duplication
        foreach ($actions as $action) {
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...
630
            $transientVars['actionId'] = $action->getId();
631
632
            if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
633
                $l[] = $action->getId();
634
            }
635
        }
636
637
        return $l;
638
    }
639
640
    /**
641
     * @param ActionDescriptor $action
642
     * @param                  $id
643
     * @param array|Traversable $currentSteps
644
     * @param                  $state
645
     *
646
     * @return void
647
     *
648
     * @throws InvalidArgumentException
649
     * @throws InternalWorkflowException
650
     */
651
    protected function completeEntry(ActionDescriptor $action = null, $id, $currentSteps, $state)
652
    {
653 View Code Duplication
        if (!($currentSteps instanceof Traversable || is_array($currentSteps))) {
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...
654
            $errMsg = 'CurrentSteps должен быть массивом, либо реализовывать интерфейс Traversable';
655
            throw new InvalidArgumentException($errMsg);
656
        }
657
658
659
        $this->getPersistence()->setEntryState($id, $state);
660
661
        $oldStatus = null !== $action ? $action->getUnconditionalResult()->getOldStatus() : 'Finished';
662
        $actionIdValue = null !== $action ? $action->getId() : -1;
663
        foreach ($currentSteps as $step) {
664
            $this->getPersistence()->markFinished($step, $actionIdValue, new DateTime(), $oldStatus, $this->context->getCaller());
665
            $this->getPersistence()->moveToHistory($step);
666
        }
667
    }
668
    /**
669
     * @param ResultDescriptor       $theResult
670
     * @param WorkflowEntryInterface $entry
671
     * @param WorkflowStoreInterface $store
672
     * @param integer                $actionId
673
     * @param StepInterface          $currentStep
674
     * @param array                  $previousIds
675
     * @param TransientVarsInterface                  $transientVars
676
     * @param PropertySetInterface   $ps
677
     *
678
     * @return StepInterface
679
     *
680
     * @throws InternalWorkflowException
681
     * @throws StoreException
682
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
683
     * @throws WorkflowException
684
     */
685
    protected function createNewCurrentStep(
686
        ResultDescriptor $theResult,
687
        WorkflowEntryInterface $entry,
688
        WorkflowStoreInterface $store,
689
        $actionId,
690
        StepInterface $currentStep = null,
691
        array $previousIds = [],
692
        TransientVarsInterface $transientVars,
693
        PropertySetInterface $ps
694
    ) {
695
        try {
696
            $nextStep = $theResult->getStep();
697
698
            if (-1 === $nextStep) {
699
                if (null !== $currentStep) {
700
                    $nextStep = $currentStep->getStepId();
701
                } else {
702
                    $errMsg = 'Неверный аргумент. Новый шаг является таким же как текущий. Но текущий шаг не указан';
703
                    throw new StoreException($errMsg);
704
                }
705
            }
706
707
            $owner = $theResult->getOwner();
708
709
            $logMsg = sprintf(
710
                'Результат: stepId=%s, status=%s, owner=%s, actionId=%s, currentStep=%s',
711
                $nextStep,
712
                $theResult->getStatus(),
713
                $owner,
714
                $actionId,
715
                null !== $currentStep ? $currentStep->getId() : 0
716
            );
717
            $this->getLog()->debug($logMsg);
718
719
            $variableResolver = $this->getConfiguration()->getVariableResolver();
720
721
            if (null !== $owner) {
722
                $o = $variableResolver->translateVariables($owner, $transientVars, $ps);
723
                $owner = null !== $o ? (string)$o : null;
724
            }
725
726
727
            $oldStatus = $theResult->getOldStatus();
728
            $oldStatus = (string)$variableResolver->translateVariables($oldStatus, $transientVars, $ps);
729
730
            $status = $theResult->getStatus();
731
            $status = (string)$variableResolver->translateVariables($status, $transientVars, $ps);
732
733
734
            if (null !== $currentStep) {
735
                $store->markFinished($currentStep, $actionId, new DateTime(), $oldStatus, $this->context->getCaller());
736
                $store->moveToHistory($currentStep);
737
            }
738
739
            $startDate = new DateTime();
740
            $dueDate = null;
741
742
            $theResultDueDate = (string)$theResult->getDueDate();
743
            $theResultDueDate = trim($theResultDueDate);
744
            if (strlen($theResultDueDate) > 0) {
745
                $dueDateObject = $variableResolver->translateVariables($theResultDueDate, $transientVars, $ps);
746
747
                if ($dueDateObject instanceof DateTime) {
748
                    $dueDate = $dueDateObject;
749
                } elseif (is_string($dueDateObject)) {
750
                    $dueDate = new DateTime($dueDate);
751
                } elseif (is_numeric($dueDateObject)) {
752
                    $dueDate = DateTime::createFromFormat('U', $dueDateObject);
753
                    if (false === $dueDate) {
754
                        $errMsg = 'Invalid due date conversion';
755
                        throw new Exception\InternalWorkflowException($errMsg);
756
                    }
757
                }
758
            }
759
760
            $newStep = $store->createCurrentStep($entry->getId(), $nextStep, $owner, $startDate, $dueDate, $status, $previousIds);
761
            $transientVars['createdStep'] =  $newStep;
762
763
            if (null === $currentStep && 0 === count($previousIds)) {
764
                $currentSteps = [];
765
                $currentSteps[] = $newStep;
766
                $transientVars['currentSteps'] =  $currentSteps;
767
            }
768
769
            if (! $transientVars->offsetExists('descriptor')) {
770
                $errMsg = 'Ошибка при получение дескриптора workflow из transientVars';
771
                throw new InternalWorkflowException($errMsg);
772
            }
773
774
            /** @var WorkflowDescriptor $descriptor */
775
            $descriptor = $transientVars['descriptor'];
776
            $step = $descriptor->getStep($nextStep);
777
778
            if (null === $step) {
779
                $errMsg = sprintf('Шаг #%s не найден', $nextStep);
780
                throw new WorkflowException($errMsg);
781
            }
782
783
            $preFunctions = $step->getPreFunctions();
784
785
            foreach ($preFunctions as $function) {
786
                $this->executeFunction($function, $transientVars, $ps);
787
            }
788
        } catch (WorkflowException $e) {
789
            $this->context->setRollbackOnly();
790
            /** @var WorkflowException $e */
791
            throw $e;
792
        }
793
    }
794
795
    /**
796
     * Создает хранилище переменных
797
     *
798
     * @param $class
799
     *
800
     * @return TransientVarsInterface
801
     */
802
    protected function transientVarsFactory($class = BaseTransientVars::class)
803
    {
804
        $r = new \ReflectionClass($class);
805
        return $r->newInstance();
806
    }
807
808
    /**
809
     *
810
     *
811
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
812
     *
813
     * @param integer $entryId id запущенного процесса workflow
814
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
815
     * @param TransientVarsInterface $inputs Входные данные для перехода
816
     *
817
     * @return void
818
     *
819
     * @throws WorkflowException
820
     * @throws InvalidActionException
821
     * @throws InvalidArgumentException
822
     * @throws InternalWorkflowException
823
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
824
     */
825
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
826
    {
827
        $actionId = (integer)$actionId;
828
        if (null === $inputs) {
829
            $inputs = $this->transientVarsFactory();
830
        }
831
        $transientVars = $inputs;
832
        $inputs = clone $transientVars;
833
834
        $store = $this->getPersistence();
835
        $entry = $store->findEntry($entryId);
836
837
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
838
            return;
839
        }
840
841
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
842
843
        $currentSteps = $store->findCurrentSteps($entryId);
844
        $action = null;
845
846
        $ps = $store->getPropertySet($entryId);
847
848
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
849
850
851
        $validAction = false;
852
853
        foreach ($wf->getGlobalActions() as $actionDesc) {
854
            if ($actionId === $actionDesc->getId()) {
855
                $action = $actionDesc;
856
857
                if ($this->isActionAvailable($action, $transientVars, $ps, 0)) {
858
                    $validAction = true;
859
                }
860
            }
861
        }
862
863
864
        foreach ($currentSteps as $step) {
865
            $s = $wf->getStep($step->getStepId());
866
867
            foreach ($s->getActions() as $actionDesc) {
868
                if (!$actionDesc instanceof ActionDescriptor) {
869
                    $errMsg = 'Invalid action descriptor';
870
                    throw new InternalWorkflowException($errMsg);
871
                }
872
873
                if ($actionId === $actionDesc->getId()) {
874
                    $action = $actionDesc;
875
876
                    if ($this->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
877
                        $validAction = true;
878
                    }
879
                }
880
            }
881
        }
882
883
884
        if (!$validAction) {
885
            $errMsg = sprintf(
886
                'Action %s is invalid',
887
                $actionId
888
            );
889
            throw new InvalidActionException($errMsg);
890
        }
891
892
893
        try {
894
            if ($this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps)) {
0 ignored issues
show
Documentation introduced by
$currentSteps is of type array<integer,object<Old...low\Spi\StepInterface>>, but the function expects a object<SplObjectStorage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
895
                $this->checkImplicitFinish($action, $entryId);
896
            }
897
        } catch (WorkflowException $e) {
898
            $this->context->setRollbackOnly();
899
            /** @var  WorkflowException $e*/
900
            throw $e;
901
        }
902
    }
903
904
    /**
905
     * @param ActionDescriptor $action
906
     * @param                  $id
907
     *
908
     * @return void
909
     *
910
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
911
     * @throws InvalidArgumentException
912
     * @throws InternalWorkflowException
913
     */
914
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
915
    {
916
        $store = $this->getPersistence();
917
        $entry = $store->findEntry($id);
918
919
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
920
921
        $currentSteps = $store->findCurrentSteps($id);
922
923
        $isCompleted = $wf->getGlobalActions()->count() === 0;
924
925
        foreach ($currentSteps as $step) {
926
            if ($isCompleted) {
927
                break;
928
            }
929
930
            $stepDes = $wf->getStep($step->getStepId());
931
932
            if ($stepDes->getActions()->count() > 0) {
933
                $isCompleted = true;
934
            }
935
        }
936
937
        if ($isCompleted) {
938
            $this->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
939
        }
940
    }
941
942
    /**
943
     *
944
     * Check if the state of the specified workflow instance can be changed to the new specified one.
945
     *
946
     * @param integer $id The workflow instance id.
947
     * @param integer $newState The new state id.
948
     *
949
     * @return boolean true if the state of the workflow can be modified, false otherwise.
950
     *
951
     * @throws InternalWorkflowException
952
     */
953
    public function canModifyEntryState($id, $newState)
954
    {
955
        $store = $this->getPersistence();
956
        $entry = $store->findEntry($id);
957
958
        $currentState = $entry->getState();
959
960
        $result = false;
961
        try {
962
            switch ($newState) {
963
                case WorkflowEntryInterface::COMPLETED: {
964
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
965
                        $result = true;
966
                    }
967
                    break;
968
                }
969
970
                //@TODO Разобраться с бизнес логикой. Может быть нужно добавить break
971
                /** @noinspection PhpMissingBreakStatementInspection */
972
                case WorkflowEntryInterface::CREATED: {
973
                    $result = false;
974
                }
975 View Code Duplication
                case WorkflowEntryInterface::ACTIVATED: {
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...
976
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
977
                        $result = true;
978
                    }
979
                    break;
980
                }
981
                case WorkflowEntryInterface::SUSPENDED: {
982
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
983
                        $result = true;
984
                    }
985
                    break;
986
                }
987 View Code Duplication
                case WorkflowEntryInterface::KILLED: {
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...
988
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::ACTIVATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
989
                        $result = true;
990
                    }
991
                    break;
992
                }
993
                default: {
994
                    $result = false;
995
                    break;
996
                }
997
998
            }
999
1000
            return $result;
1001
        } catch (StoreException $e) {
0 ignored issues
show
Unused Code introduced by
catch (\OldTown\Workflow...($errMsg, array($e)); } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1002
            $errMsg = sprintf(
1003
                'Ошибка проверки изменения состояния для инстанса #%s',
1004
                $id
1005
            );
1006
            $this->getLog()->error($errMsg, [$e]);
1007
        }
1008
1009
        return false;
1010
    }
1011
1012
1013
    /**
1014
     *
1015
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
1016
     *
1017
     * @param integer $entryId id экземпляра workflow
1018
     *
1019
     * @return SplObjectStorage|StepInterface[]
1020
     *
1021
     * @throws InternalWorkflowException
1022
     */
1023
    public function getCurrentSteps($entryId)
1024
    {
1025
        return $this->getStepFromStorage($entryId, static::CURRENT_STEPS);
1026
    }
1027
1028
    /**
1029
     * Возвращает информацию о том в какие шаги, были осуществленны переходы, для процесса workflow с заданным id
1030
     *
1031
     * @param integer $entryId уникальный идентификатор процесса workflow
1032
     *
1033
     * @return StepInterface[]|SplObjectStorage список шагов
1034
     *
1035
     * @throws InternalWorkflowException
1036
     */
1037
    public function getHistorySteps($entryId)
1038
    {
1039
        return $this->getStepFromStorage($entryId, static::HISTORY_STEPS);
1040
    }
1041
1042
    /**
1043
     * Получение шагов информации о шагах процесса workflow
1044
     *
1045
     * @param $entryId
1046
     * @param $type
1047
     *
1048
     * @return Spi\StepInterface[]|SplObjectStorage
1049
     *
1050
     * @throws InternalWorkflowException
1051
     */
1052
    protected function getStepFromStorage($entryId, $type)
1053
    {
1054
        try {
1055
            $store = $this->getPersistence();
1056
1057
            if (static::CURRENT_STEPS === $type) {
1058
                return $store->findCurrentSteps($entryId);
1059
            } elseif (static::HISTORY_STEPS === $type) {
1060
                return $store->findHistorySteps($entryId);
1061
            }
1062
        } catch (StoreException $e) {
1063
            $errMsg = sprintf(
1064
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
1065
                $entryId
1066
            );
1067
            $this->getLog()->error($errMsg, [$e]);
1068
        }
1069
1070
        return new SplObjectStorage();
1071
    }
1072
1073
1074
1075
    /**
1076
     *
1077
     *
1078
     * Modify the state of the specified workflow instance.
1079
     * @param integer $id The workflow instance id.
1080
     * @param integer $newState the new state to change the workflow instance to.
1081
     *
1082
     * @throws InvalidArgumentException
1083
     * @throws InvalidEntryStateException
1084
     * @throws InternalWorkflowException
1085
     */
1086
    public function changeEntryState($id, $newState)
1087
    {
1088
        $store = $this->getPersistence();
1089
        $entry = $store->findEntry($id);
1090
1091
        if ($newState === $entry->getState()) {
1092
            return;
1093
        }
1094
1095
        if ($this->canModifyEntryState($id, $newState)) {
1096
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
1097
                $currentSteps = $this->getCurrentSteps($id);
1098
1099
                if (count($currentSteps) > 0) {
1100
                    $this->completeEntry(null, $id, $currentSteps, $newState);
1101
                }
1102
            }
1103
1104
            $store->setEntryState($id, $newState);
1105
        } else {
1106
            $errMsg = sprintf(
1107
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
1108
                $id,
1109
                $entry->getState(),
1110
                $newState
1111
            );
1112
1113
            throw new InvalidEntryStateException($errMsg);
1114
        }
1115
1116
        $msg = sprintf(
1117
            '%s : Новое состояние: %s',
1118
            $entry->getId(),
1119
            $entry->getState()
1120
        );
1121
        $this->getLog()->debug($msg);
1122
    }
1123
1124
1125
    /**
1126
     * @param FunctionDescriptor $function
1127
     * @param TransientVarsInterface $transientVars
1128
     * @param PropertySetInterface $ps
1129
     *
1130
     * @throws WorkflowException
1131
     * @throws InternalWorkflowException
1132
     */
1133
    protected function executeFunction(FunctionDescriptor $function, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1134
    {
1135
        if (null !== $function) {
1136
            $type = $function->getType();
1137
1138
            $argsOriginal = $function->getArgs();
1139
            $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
1140
1141
            $provider = $this->getResolver()->getFunction($type, $args);
1142
1143
            if (null === $provider) {
1144
                $this->context->setRollbackOnly();
1145
                $errMsg = 'Не загружен провайдер для функции';
1146
                throw new WorkflowException($errMsg);
1147
            }
1148
1149
            try {
1150
                $provider->execute($transientVars, $args, $ps);
1151
            } catch (WorkflowException $e) {
1152
                $this->context->setRollbackOnly();
1153
                /** @var  WorkflowException $e*/
1154
                throw $e;
1155
            }
1156
        }
1157
    }
1158
1159
1160
    /**
1161
     * @param WorkflowEntryInterface $entry
1162
     * @param $validatorsStorage
1163
     * @param TransientVarsInterface $transientVars
1164
     * @param PropertySetInterface $ps
1165
     *
1166
     * @throws WorkflowException
1167
     * @throws InvalidArgumentException
1168
     * @throws InternalWorkflowException
1169
     * @throws InvalidInputException
1170
     */
1171
    protected function verifyInputs(WorkflowEntryInterface $entry, $validatorsStorage, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1172
    {
1173 View Code Duplication
        if ($validatorsStorage 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...
1174
            $validators = [];
1175
            foreach ($validatorsStorage as $k => $v) {
1176
                $validators[$k] = $v;
1177
            }
1178
        } elseif (is_array($validatorsStorage)) {
1179
            $validators = $validatorsStorage;
1180
        } else {
1181
            $errMsg = sprintf(
1182
                'Validators должен быть массивом, либо реализовывать интерфейс Traversable. EntryId: %s',
1183
                $entry->getId()
1184
            );
1185
            throw new InvalidArgumentException($errMsg);
1186
        }
1187
1188
        /** @var ValidatorDescriptor[] $validators */
1189
        foreach ($validators as $input) {
1190
            if (null !== $input) {
1191
                $type = $input->getType();
1192
                $argsOriginal = $input->getArgs();
1193
1194
                $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
1195
1196
1197
                $validator = $this->getResolver()->getValidator($type, $args);
1198
1199
                if (null === $validator) {
1200
                    $this->context->setRollbackOnly();
1201
                    $errMsg = 'Ошибка при загрузке валидатора';
1202
                    throw new WorkflowException($errMsg);
1203
                }
1204
1205
                try {
1206
                    $validator->validate($transientVars, $args, $ps);
1207
                } catch (InvalidInputException $e) {
1208
                    /** @var  InvalidInputException $e*/
1209
                    throw $e;
1210
                } catch (\Exception $e) {
1211
                    $this->context->setRollbackOnly();
1212
1213
                    if ($e instanceof WorkflowException) {
1214
                        /** @var  WorkflowException $e*/
1215
                        throw $e;
1216
                    }
1217
1218
                    throw new WorkflowException($e->getMessage(), $e->getCode(), $e);
1219
                }
1220
            }
1221
        }
1222
    }
1223
1224
1225
    /**
1226
     * Возвращает текущий шаг
1227
     *
1228
     * @param WorkflowDescriptor $wfDesc
1229
     * @param integer $actionId
1230
     * @param StepInterface[]|SplObjectStorage $currentSteps
1231
     * @param TransientVarsInterface $transientVars
1232
     * @param PropertySetInterface $ps
1233
     *
1234
     * @return StepInterface
1235
     *
1236
     * @throws InternalWorkflowException
1237
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1238
     * @throws WorkflowException
1239
     */
1240
    protected function getCurrentStep(WorkflowDescriptor $wfDesc, $actionId, SplObjectStorage $currentSteps, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1241
    {
1242
        if (1 === $currentSteps->count()) {
1243
            $currentSteps->rewind();
1244
            return $currentSteps->current();
1245
        }
1246
1247
1248
        foreach ($currentSteps as $step) {
1249
            $stepId = $step->getId();
1250
            $action = $wfDesc->getStep($stepId)->getAction($actionId);
1251
1252
            if ($this->isActionAvailable($action, $transientVars, $ps, $stepId)) {
1253
                return $step;
1254
            }
1255
        }
1256
1257
        return null;
1258
    }
1259
1260
    /**
1261
     * @param ActionDescriptor|null $action
1262
     * @param TransientVarsInterface $transientVars
1263
     * @param PropertySetInterface $ps
1264
     * @param $stepId
1265
     *
1266
     * @return boolean
1267
     *
1268
     * @throws InternalWorkflowException
1269
     * @throws InternalWorkflowException
1270
     * @throws WorkflowException
1271
     */
1272
    protected function isActionAvailable(ActionDescriptor $action = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $stepId)
1273
    {
1274
        if (null === $action) {
1275
            return false;
1276
        }
1277
1278
        $result = null;
1279
        $actionHash = spl_object_hash($action);
1280
1281
        $result = array_key_exists($actionHash, $this->stateCache) ? $this->stateCache[$actionHash] : $result;
1282
1283
        $wf = $this->getWorkflowDescriptorForAction($action);
1284
1285
1286
        if (null === $result) {
1287
            $restriction = $action->getRestriction();
1288
            $conditions = null;
1289
1290
            if (null !== $restriction) {
1291
                $conditions = $restriction->getConditionsDescriptor();
1292
            }
1293
1294
            $result = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $stepId)
1295
                && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $stepId);
1296
1297
            $this->stateCache[$actionHash] = $result;
1298
        }
1299
1300
1301
        $result = (boolean)$result;
1302
1303
        return $result;
1304
    }
1305
1306
    /**
1307
     * По дейсвтию получаем дексрипторв workflow
1308
     *
1309
     * @param ActionDescriptor $action
1310
     *
1311
     * @return WorkflowDescriptor
1312
     *
1313
     * @throws InternalWorkflowException
1314
     */
1315
    private function getWorkflowDescriptorForAction(ActionDescriptor $action)
1316
    {
1317
        $objWfd = $action;
1318
1319
        $count = 0;
1320
        while (!$objWfd instanceof WorkflowDescriptor || null === $objWfd) {
1321
            $objWfd = $objWfd->getParent();
1322
1323
            $count++;
1324
            if ($count > 10) {
1325
                $errMsg = 'Ошибка при получение WorkflowDescriptor';
1326
                throw new InternalWorkflowException($errMsg);
1327
            }
1328
        }
1329
1330
        return $objWfd;
1331
    }
1332
1333
1334
    /**
1335
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1336
     *
1337
     * @param string $workflowName имя workflow
1338
     * @param integer $initialAction id начального состояния
1339
     * @param TransientVarsInterface $inputs
1340
     *
1341
     * @return bool
1342
     *
1343
     * @throws InvalidArgumentException
1344
     * @throws WorkflowException
1345
     * @throws InternalWorkflowException
1346
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1347
     */
1348
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
1349
    {
1350
        $mockWorkflowName = $workflowName;
1351
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
1352
1353
        try {
1354
            $ps = PropertySetManager::getInstance('memory', null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1355
            if (!$ps instanceof PropertySetInterface) {
1356
                $errMsg = 'Invalid create PropertySet';
1357
                throw new InternalWorkflowException($errMsg);
1358
            }
1359
        } catch (\Exception $e) {
1360
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
1361
        }
1362
1363
1364
1365
1366
        if (null === $inputs) {
1367
            $inputs = $this->transientVarsFactory();
1368
        }
1369
        $transientVars = $inputs;
1370
1371
        try {
1372
            $this->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
1373
1374
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
1375
1376
            return $result;
1377
        } catch (InvalidActionException $e) {
1378
            $this->getLog()->error($e->getMessage(), [$e]);
1379
1380
            return false;
1381
        } catch (WorkflowException $e) {
1382
            $errMsg = sprintf(
1383
                'Ошибка при проверки canInitialize: %s',
1384
                $e->getMessage()
1385
            );
1386
            $this->getLog()->error($errMsg, [$e]);
1387
1388
            return false;
1389
        }
1390
    }
1391
1392
1393
    /**
1394
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1395
     *
1396
     * @param string $workflowName имя workflow
1397
     * @param integer $initialAction id начального состояния
1398
     * @param TransientVarsInterface $transientVars
1399
     *
1400
     * @param PropertySetInterface $ps
1401
     *
1402
     * @return bool
1403
     *
1404
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1405
     * @throws InvalidActionException
1406
     * @throws InternalWorkflowException
1407
     * @throws WorkflowException
1408
     */
1409
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1410
    {
1411
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
1412
1413
        $actionDescriptor = $wf->getInitialAction($initialAction);
1414
1415
        if (null === $actionDescriptor) {
1416
            $errMsg = sprintf(
1417
                'Некорректное инициирующие действие # %s',
1418
                $initialAction
1419
            );
1420
            throw new InvalidActionException($errMsg);
1421
        }
1422
1423
        $restriction = $actionDescriptor->getRestriction();
1424
1425
1426
        $conditions = null;
1427
        if (null !== $restriction) {
1428
            $conditions = $restriction->getConditionsDescriptor();
1429
        }
1430
1431
        $passesConditions = $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1432
1433
        return $passesConditions;
1434
    }
1435
1436
    /**
1437
     * Возвращает резолвер
1438
     *
1439
     * @return TypeResolverInterface
1440
     */
1441
    public function getResolver()
1442
    {
1443
        if (null !== $this->typeResolver) {
1444
            return $this->typeResolver;
1445
        }
1446
1447
        $classResolver = $this->getDefaultTypeResolverClass();
1448
        $r = new ReflectionClass($classResolver);
1449
        $resolver = $r->newInstance();
1450
        $this->typeResolver = $resolver;
1451
1452
        return $this->typeResolver;
1453
    }
1454
1455
    /**
1456
     * Возвращает хранилище состояния workflow
1457
     *
1458
     * @return WorkflowStoreInterface
1459
     *
1460
     * @throws InternalWorkflowException
1461
     */
1462
    protected function getPersistence()
1463
    {
1464
        return $this->getConfiguration()->getWorkflowStore();
1465
    }
1466
1467
    /**
1468
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
1469
     * инициализирует ее.
1470
     *
1471
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
1472
     *
1473
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
1474
     *
1475
     * @throws InternalWorkflowException
1476
     */
1477
    public function getConfiguration()
1478
    {
1479
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
1480
1481
        if (!$config->isInitialized()) {
1482
            try {
1483
                $config->load(null);
1484
            } catch (FactoryException $e) {
1485
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
1486
                $this->getLog()->critical($errMsg, ['exception' => $e]);
1487
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1488
            }
1489
        }
1490
1491
        return $config;
1492
    }
1493
1494
    /**
1495
     * @return LoggerInterface
1496
     */
1497
    public function getLog()
1498
    {
1499
        return $this->log;
1500
    }
1501
1502
    /**
1503
     * @param LoggerInterface $log
1504
     *
1505
     * @return $this
1506
     *
1507
     * @throws InternalWorkflowException
1508
     */
1509
    public function setLog($log)
1510
    {
1511
        try {
1512
            LogFactory::validLogger($log);
1513
        } catch (\Exception $e) {
1514
            $errMsg = 'Ошибка при валидации логера';
1515
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1516
        }
1517
1518
1519
        $this->log = $log;
1520
1521
        return $this;
1522
    }
1523
1524
1525
    /**
1526
     * Get the workflow descriptor for the specified workflow name.
1527
     *
1528
     * @param string $workflowName The workflow name.
1529
     * @return WorkflowDescriptor
1530
     *
1531
     * @throws InternalWorkflowException
1532
     */
1533
    public function getWorkflowDescriptor($workflowName)
1534
    {
1535
        try {
1536
            return $this->getConfiguration()->getWorkflow($workflowName);
1537
        } catch (FactoryException $e) {
1538
            $errMsg = 'Ошибка при загрузке workflow';
1539
            $this->getLog()->error($errMsg, ['exception' => $e]);
1540
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1541
        }
1542
    }
1543
1544
1545
    /**
1546
     * Executes a special trigger-function using the context of the given workflow instance id.
1547
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
1548
     *
1549
     * @param integer $id The workflow instance id
1550
     * @param integer $triggerId The id of the special trigger-function
1551
     *
1552
     * @throws InvalidArgumentException
1553
     * @throws WorkflowException
1554
     * @throws InternalWorkflowException
1555
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1556
     */
1557
    public function executeTriggerFunction($id, $triggerId)
1558
    {
1559
        $store = $this->getPersistence();
1560
        $entry = $store->findEntry($id);
1561
1562
        if (null === $entry) {
1563
            $errMsg = sprintf(
1564
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
1565
                $triggerId,
1566
                $id
1567
            );
1568
            $this->getLog()->warning($errMsg);
1569
            return;
1570
        }
1571
1572
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1573
1574
        $ps = $store->getPropertySet($id);
1575
        $transientVars = $this->transientVarsFactory();
1576
1577
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
1578
1579
        $this->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
1580
    }
1581
1582
    /**
1583
     * @param $id
1584
     * @param $inputs
1585
     *
1586
     * @return array
1587
     *
1588
     */
1589
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
1590
    {
1591
        try {
1592
            $store = $this->getPersistence();
1593
            $entry = $store->findEntry($id);
1594
1595
            if (null === $entry) {
1596
                $errMsg = sprintf(
1597
                    'Не существует экземпляра workflow c id %s',
1598
                    $id
1599
                );
1600
                throw new InvalidArgumentException($errMsg);
1601
            }
1602
1603
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
1604
                return [];
1605
            }
1606
1607
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1608
1609 View Code Duplication
            if (null === $wf) {
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...
1610
                $errMsg = sprintf(
1611
                    'Не существует workflow c именем %s',
1612
                    $entry->getWorkflowName()
1613
                );
1614
                throw new InvalidArgumentException($errMsg);
1615
            }
1616
1617
            $l = [];
1618
            $ps = $store->getPropertySet($id);
1619
1620
            $transientVars = $inputs;
1621
            if (null === $transientVars) {
1622
                $transientVars = $this->transientVarsFactory();
1623
            }
1624
1625
            $currentSteps = $store->findCurrentSteps($id);
1626
1627
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
1628
1629
            $globalActions = $wf->getGlobalActions();
1630
1631
            foreach ($globalActions as $action) {
1632
                $restriction = $action->getRestriction();
1633
                $conditions = null;
1634
1635
                $transientVars['actionId'] = $action->getId();
1636
1637
                if (null !== $restriction) {
1638
                    $conditions = $restriction->getConditionsDescriptor();
1639
                }
1640
1641
                $flag = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1642
                if ($flag) {
1643
                    $l[] = $action->getId();
1644
                }
1645
            }
1646
1647
1648 View Code Duplication
            foreach ($currentSteps as $step) {
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...
1649
                $data = $this->getAvailableActionsForStep($wf, $step, $transientVars, $ps);
1650
                foreach ($data as $v) {
1651
                    $l[] = $v;
1652
                }
1653
            }
1654
            return array_unique($l);
1655
        } catch (\Exception $e) {
1656
            $errMsg = 'Ошибка проверки доступных действий';
1657
            $this->getLog()->error($errMsg, [$e]);
1658
        }
1659
1660
        return [];
1661
    }
1662
1663
    /**
1664
     * @param WorkflowDescriptor   $wf
1665
     * @param StepInterface        $step
1666
     * @param TransientVarsInterface                $transientVars
1667
     * @param PropertySetInterface $ps
1668
     *
1669
     * @return array
1670
     *
1671
     * @throws InternalWorkflowException
1672
     * @throws WorkflowException
1673
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1674
     */
1675
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1676
    {
1677
        $l = [];
1678
        $s = $wf->getStep($step->getStepId());
1679
1680
        if (null === $s) {
1681
            $errMsg = sprintf(
1682
                'getAvailableActionsForStep вызван с не существующим id шага %s',
1683
                $step->getStepId()
1684
            );
1685
1686
            $this->getLog()->warning($errMsg);
1687
1688
            return $l;
1689
        }
1690
1691
        $actions  = $s->getActions();
1692
1693
        if (null === $actions || 0  === $actions->count()) {
1694
            return $l;
1695
        }
1696
1697
        foreach ($actions as $action) {
1698
            $restriction = $action->getRestriction();
1699
            $conditions = null;
1700
1701
            $transientVars['actionId'] = $action->getId();
1702
1703
1704
            if (null !== $restriction) {
1705
                $conditions = $restriction->getConditionsDescriptor();
1706
            }
1707
1708
            $f = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
1709
                 && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
1710
            if ($f) {
1711
                $l[] = $action->getId();
1712
            }
1713
        }
1714
1715
        return $l;
1716
    }
1717
1718
    /**
1719
     * @param ConfigurationInterface $configuration
1720
     *
1721
     * @return $this
1722
     */
1723
    public function setConfiguration(ConfigurationInterface $configuration)
1724
    {
1725
        $this->configuration = $configuration;
1726
1727
        return $this;
1728
    }
1729
1730
    /**
1731
     * Возвращает состояние для текущего экземпляра workflow
1732
     *
1733
     * @param integer $id id экземпляра workflow
1734
     * @return integer id текущего состояния
1735
     *
1736
     * @throws InternalWorkflowException
1737
     */
1738
    public function getEntryState($id)
1739
    {
1740
        try {
1741
            $store = $this->getPersistence();
1742
1743
            return $store->findEntry($id)->getState();
1744
        } catch (StoreException $e) {
1745
            $errMsg = sprintf(
1746
                'Ошибка при получение состояния экземпляра workflow c id# %s',
1747
                $id
1748
            );
1749
            $this->getLog()->error($errMsg, [$e]);
1750
        }
1751
1752
        return WorkflowEntryInterface::UNKNOWN;
1753
    }
1754
1755
1756
    /**
1757
     * Настройки хранилища
1758
     *
1759
     * @return array
1760
     *
1761
     * @throws InternalWorkflowException
1762
     */
1763
    public function getPersistenceProperties()
1764
    {
1765
        return $this->getConfiguration()->getPersistenceArgs();
1766
    }
1767
1768
1769
    /**
1770
     * Get the PropertySet for the specified workflow instance id.
1771
     * @param integer $id The workflow instance id.
1772
     *
1773
     * @return PropertySetInterface
1774
     * @throws InternalWorkflowException
1775
     */
1776
    public function getPropertySet($id)
1777
    {
1778
        $ps = null;
1779
1780
        try {
1781
            $ps = $this->getPersistence()->getPropertySet($id);
1782
        } catch (StoreException $e) {
1783
            $errMsg = sprintf(
1784
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1785
                $id
1786
            );
1787
            $this->getLog()->error($errMsg, [$e]);
1788
        }
1789
1790
        return $ps;
1791
    }
1792
1793
    /**
1794
     * @return string[]
1795
     *
1796
     * @throws InternalWorkflowException
1797
     */
1798
    public function getWorkflowNames()
1799
    {
1800
        try {
1801
            return $this->getConfiguration()->getWorkflowNames();
1802
        } catch (FactoryException $e) {
1803
            $errMsg = 'Ошибка при получение имен workflow';
1804
            $this->getLog()->error($errMsg, [$e]);
1805
        }
1806
1807
        return [];
1808
    }
1809
1810
    /**
1811
     * @param TypeResolverInterface $typeResolver
1812
     *
1813
     * @return $this
1814
     */
1815
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1816
    {
1817
        $this->typeResolver = $typeResolver;
1818
1819
        return $this;
1820
    }
1821
1822
1823
    /**
1824
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1825
     * @param integer $id id the workflow instance id.
1826
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1827
     *
1828
     * @return array  A List of permissions specified currently (a permission is a string name).
1829
     *
1830
     */
1831
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1832
    {
1833
        try {
1834
            $store = $this->getPersistence();
1835
            $entry = $store->findEntry($id);
1836
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1837
1838
            $ps = $store->getPropertySet($id);
1839
1840
            if (null === $inputs) {
1841
                $inputs = $this->transientVarsFactory();
1842
            }
1843
            $transientVars = $inputs;
1844
1845
            $currentSteps = $store->findCurrentSteps($id);
1846
1847
            try {
1848
                $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1849
            } catch (\Exception $e) {
1850
                $errMsg = sprintf(
1851
                    'Внутреннея ошибка: %s',
1852
                    $e->getMessage()
1853
                );
1854
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1855
            }
1856
1857
1858
            $s = [];
1859
1860
            foreach ($currentSteps as $step) {
1861
                $stepId = $step->getStepId();
1862
1863
                $xmlStep = $wf->getStep($stepId);
1864
1865
                $securities = $xmlStep->getPermissions();
1866
1867
                foreach ($securities as $security) {
1868
                    if (!$security instanceof PermissionDescriptor) {
1869
                        $errMsg = 'Invalid PermissionDescriptor';
1870
                        throw new InternalWorkflowException($errMsg);
1871
                    }
1872
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1873
                    if (null !== $security->getRestriction() && $this->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1874
                        $s[$security->getName()] = $security->getName();
1875
                    }
1876
                }
1877
            }
1878
1879
            return $s;
1880
        } catch (\Exception $e) {
1881
            $errMsg = sprintf(
1882
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1883
                $id
1884
            );
1885
            $this->getLog()->error($errMsg, [$e]);
1886
        }
1887
1888
        return [];
1889
    }
1890
1891
1892
    /**
1893
     * Get the name of the specified workflow instance.
1894
     *
1895
     * @param integer $id the workflow instance id.
1896
     *
1897
     * @return string
1898
     *
1899
     * @throws InternalWorkflowException
1900
     */
1901
    public function getWorkflowName($id)
1902
    {
1903
        try {
1904
            $store = $this->getPersistence();
1905
            $entry = $store->findEntry($id);
1906
1907
            if (null !== $entry) {
1908
                return $entry->getWorkflowName();
1909
            }
1910
        } catch (FactoryException $e) {
1911
            $errMsg = sprintf(
1912
                'Ошибка при получение имен workflow для инстанса с id # %s',
1913
                $id
1914
            );
1915
            $this->getLog()->error($errMsg, [$e]);
1916
        }
1917
1918
        return null;
1919
    }
1920
1921
    /**
1922
     * Удаляет workflow
1923
     *
1924
     * @param string $workflowName
1925
     *
1926
     * @return bool
1927
     *
1928
     * @throws InternalWorkflowException
1929
     */
1930
    public function removeWorkflowDescriptor($workflowName)
1931
    {
1932
        return $this->getConfiguration()->removeWorkflow($workflowName);
1933
    }
1934
1935
    /**
1936
     * @param                    $workflowName
1937
     * @param WorkflowDescriptor $descriptor
1938
     * @param                    $replace
1939
     *
1940
     * @return bool
1941
     *
1942
     * @throws InternalWorkflowException
1943
     */
1944
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1945
    {
1946
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1947
1948
        return $success;
1949
    }
1950
1951
1952
    /**
1953
     * Query the workflow store for matching instances
1954
     *
1955
     * @param WorkflowExpressionQuery $query
1956
     *
1957
     * @return array
1958
     *
1959
     * @throws InternalWorkflowException
1960
     */
1961
    public function query(WorkflowExpressionQuery $query)
1962
    {
1963
        return $this->getPersistence()->query($query);
1964
    }
1965
1966
    /**
1967
     * @return string
1968
     */
1969
    public function getDefaultTypeResolverClass()
1970
    {
1971
        return $this->defaultTypeResolverClass;
1972
    }
1973
1974
    /**
1975
     * @param string $defaultTypeResolverClass
1976
     *
1977
     * @return $this
1978
     */
1979
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1980
    {
1981
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1982
1983
        return $this;
1984
    }
1985
1986
1987
    /**
1988
     * @param ConditionsDescriptor $descriptor
1989
     * @param TransientVarsInterface $transientVars
1990
     * @param PropertySetInterface $ps
1991
     * @param                      $currentStepId
1992
     *
1993
     * @return bool
1994
     *
1995
     * @throws InternalWorkflowException
1996
     * @throws WorkflowException
1997
     */
1998
    protected function passesConditionsByDescriptor(ConditionsDescriptor $descriptor = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
1999
    {
2000
        if (null === $descriptor) {
2001
            return true;
2002
        }
2003
2004
        $type = $descriptor->getType();
2005
        $conditions = $descriptor->getConditions();
2006
        if (!$conditions instanceof SplObjectStorage) {
2007
            $errMsg = 'Invalid conditions';
2008
            throw new InternalWorkflowException($errMsg);
2009
        }
2010
        $passesConditions = $this->passesConditionsWithType($type, $conditions, $transientVars, $ps, $currentStepId);
2011
2012
        return $passesConditions;
2013
    }
2014
2015
    /**
2016
     * @param string $conditionType
2017
     * @param SplObjectStorage $conditions
2018
     * @param TransientVarsInterface $transientVars
2019
     * @param PropertySetInterface $ps
2020
     * @param integer $currentStepId
2021
     *
2022
     * @return bool
2023
     *
2024
     * @throws InternalWorkflowException
2025
     * @throws InternalWorkflowException
2026
     * @throws WorkflowException
2027
     *
2028
     */
2029
    protected function passesConditionsWithType($conditionType, SplObjectStorage $conditions = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2030
    {
2031
        if (null === $conditions) {
2032
            return true;
2033
        }
2034
2035
        if (0 === $conditions->count()) {
2036
            return true;
2037
        }
2038
2039
        $and = strtoupper($conditionType) === 'AND';
2040
        $or = !$and;
2041
2042
        foreach ($conditions as $descriptor) {
2043
            if ($descriptor instanceof ConditionsDescriptor) {
2044
                $descriptorConditions = $descriptor->getConditions();
2045
                if (!$descriptorConditions instanceof SplObjectStorage) {
2046
                    $errMsg = 'Invalid conditions container';
2047
                    throw new InternalWorkflowException($errMsg);
2048
                }
2049
2050
                $result = $this->passesConditionsWithType($descriptor->getType(), $descriptorConditions, $transientVars, $ps, $currentStepId);
2051
            } elseif ($descriptor instanceof ConditionDescriptor) {
2052
                $result = $this->passesCondition($descriptor, $transientVars, $ps, $currentStepId);
2053
            } else {
2054
                $errMsg = 'Invalid condition descriptor';
2055
                throw new Exception\InternalWorkflowException($errMsg);
2056
            }
2057
2058
            if ($and && !$result) {
2059
                return false;
2060
            } elseif ($or && $result) {
2061
                return true;
2062
            }
2063
        }
2064
2065
        if ($and) {
2066
            return true;
2067
        }
2068
2069
        return false;
2070
    }
2071
2072
    /**
2073
     * @param ConditionDescriptor $conditionDesc
2074
     * @param TransientVarsInterface $transientVars
2075
     * @param PropertySetInterface $ps
2076
     * @param integer $currentStepId
2077
     *
2078
     * @return boolean
2079
     *
2080
     * @throws WorkflowException
2081
     * @throws InternalWorkflowException
2082
     */
2083
    protected function passesCondition(ConditionDescriptor $conditionDesc, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2084
    {
2085
        $type = $conditionDesc->getType();
2086
2087
        $argsOriginal = $conditionDesc->getArgs();
2088
2089
2090
        $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
2091
2092
        if (-1 !== $currentStepId) {
2093
            $stepId = array_key_exists('stepId', $args) ? (integer)$args['stepId'] : null;
2094
2095
            if (null !== $stepId && -1 === $stepId) {
2096
                $args['stepId'] = $currentStepId;
2097
            }
2098
        }
2099
2100
        $condition = $this->getResolver()->getCondition($type, $args);
2101
2102
        if (null === $condition) {
2103
            $this->context->setRollbackOnly();
2104
            $errMsg = 'Огибка при загрузки условия';
2105
            throw new WorkflowException($errMsg);
2106
        }
2107
2108
        try {
2109
            $passed = $condition->passesCondition($transientVars, $args, $ps);
2110
2111
            if ($conditionDesc->isNegate()) {
2112
                $passed = !$passed;
2113
            }
2114
        } catch (\Exception $e) {
2115
            $this->context->setRollbackOnly();
2116
2117
            $errMsg = sprintf(
2118
                'Ошбика при выполнение условия %s',
2119
                get_class($condition)
2120
            );
2121
2122
            throw new WorkflowException($errMsg, $e->getCode(), $e);
2123
        }
2124
2125
        return $passed;
2126
    }
2127
2128
    /**
2129
     * Подготавливает аргументы.
2130
     *
2131
     * @param array                  $argsOriginal
2132
     *
2133
     * @param TransientVarsInterface $transientVars
2134
     * @param PropertySetInterface   $ps
2135
     *
2136
     * @return array
2137
     *
2138
     * @throws InternalWorkflowException
2139
     */
2140
    protected function prepareArgs(array $argsOriginal = [], TransientVarsInterface $transientVars, PropertySetInterface $ps)
2141
    {
2142
        $args = [];
2143
        foreach ($argsOriginal as $key => $value) {
2144
            $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($value, $transientVars, $ps);
2145
            $args[$key] = $translateValue;
2146
        }
2147
2148
        return $args;
2149
    }
2150
}
2151