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 ( 774ec1...36e137 )
by Андрей
06:09
created

AbstractWorkflow::buildListIdsAvailableActions()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.2017

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 16
ccs 7
cts 11
cp 0.6364
rs 8.8571
cc 5
eloc 9
nc 4
nop 4
crap 6.2017
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 19
    public function __construct()
104
    {
105 19
        $this->initLoger();
106 19
    }
107
108
    /**
109
     * Инициализация системы логирования
110
     *
111
     * @throws InternalWorkflowException
112
     */
113 19
    protected function initLoger()
114
    {
115
        try {
116 19
            $this->log = LogFactory::getLog();
117 19
        } catch (\Exception $e) {
118
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
119
        }
120 19
    }
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 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
140
    {
141
        try {
142 19
            $initialAction = (integer)$initialAction;
143
144 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
145
146 19
            $store = $this->getPersistence();
147
148 19
            $entry = $store->createEntry($workflowName);
149
150 19
            $ps = $store->getPropertySet($entry->getId());
151
152
153 19
            if (null === $inputs) {
154
                $inputs = $this->transientVarsFactory();
155
            }
156 19
            $transientVars = $inputs;
157 19
            $inputs = clone $transientVars;
158
159 19
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
160
161 19
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
162 1
                $this->context->setRollbackOnly();
163 1
                $errMsg = 'You are restricted from initializing this workflow';
164 1
                throw new InvalidRoleException($errMsg);
165
            }
166
167 18
            $action = $wf->getInitialAction($initialAction);
168
169 18
            if (null === $action) {
170
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
171
                throw new InvalidActionException($errMsg);
172
            }
173
174 18
            $currentSteps = new SplObjectStorage();
175 18
            $this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
176
177 16
            $entryId = $entry->getId();
178 19
        } 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 16
        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 19
    protected function populateTransientMap(WorkflowEntryInterface $entry, TransientVarsInterface $transientVars, $registersStorage, $actionId = null, $currentSteps, PropertySetInterface $ps)
205
    {
206 19
        $this->validateIterateData($currentSteps);
207
208 19
        $registers = $this->convertDataInArray($registersStorage);
209
210
211
        /** @var RegisterDescriptor[] $registers */
212
213 19
        $transientVars['context'] = $this->context;
214 19
        $transientVars['entry'] = $entry;
215 19
        $transientVars['entryId'] = $entry->getId();
216 19
        $transientVars['store'] = $this->getPersistence();
217 19
        $transientVars['configuration'] = $this->getConfiguration();
218 19
        $transientVars['descriptor'] = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
219
220 19
        if (null !== $actionId) {
221 19
            $transientVars['actionId'] = $actionId;
222 19
        }
223
224 19
        $transientVars['currentSteps'] = $currentSteps;
225
226
227 19
        foreach ($registers as $register) {
228 3
            $args = $register->getArgs();
229 3
            $type = $register->getType();
230
231
            try {
232 3
                $r = $this->getResolver()->getRegister($type, $args);
233 3
            } catch (\Exception $e) {
234
                $errMsg = 'Ошибка при инициализации register';
235
                $this->context->setRollbackOnly();
236
                throw new WorkflowException($errMsg, $e->getCode(), $e);
237
            }
238
239 3
            $variableName = $register->getVariableName();
240
            try {
241 3
                $value = $r->registerVariable($this->context, $entry, $args, $ps);
242
243 3
                $transientVars[$variableName] = $value;
244 3
            } catch (\Exception $e) {
245
                $this->context->setRollbackOnly();
246
247
                $errMsg = sprintf(
248
                    'При получение значения переменной %s из registry %s произошла ошибка',
249
                    $variableName,
250
                    get_class($r)
251 5
                );
252
253
                throw new WorkflowException($errMsg, $e->getCode(), $e);
254
            }
255 19
        }
256
257 19
        return $transientVars;
258
    }
259
260
    /**
261
     * Проверка того что данные могут быть использованы в цикле
262
     *
263
     * @param $data
264
     *
265
     * @throws InvalidArgumentException
266
     */
267 19
    protected function validateIterateData($data)
268
    {
269 19
        if (!is_array($data) && !$data  instanceof Traversable) {
270
            $errMsg = 'Data not iterate';
271
            throw new InvalidArgumentException($errMsg);
272
        }
273 19
    }
274
275
    /**
276
     * Преобразование данных в массив
277
     *
278
     * @param $data
279
     *
280
     * @return array
281
     *
282
     * @throws InvalidArgumentException
283
     */
284 19
    protected function convertDataInArray($data)
285
    {
286 19
        $result = [];
287 19
        if ($data instanceof Traversable) {
288 19
            foreach ($data as $k => $v) {
289 7
                $result[$k] = $v;
290 19
            }
291 19
        } elseif (is_array($data)) {
292
            $result = $data;
293
        } else {
294
            $errMsg = 'Data must be an array or an interface to implement Traversable';
295
            throw new InvalidArgumentException($errMsg);
296
        }
297
298 19
        return $result;
299
    }
300
301
302
    /**
303
     * Переход между двумя статусами
304
     *
305
     * @param WorkflowEntryInterface $entry
306
     * @param SplObjectStorage|StepInterface[] $currentSteps
307
     * @param WorkflowStoreInterface $store
308
     * @param WorkflowDescriptor $wf
309
     * @param ActionDescriptor $action
310
     * @param TransientVarsInterface $transientVars
311
     * @param TransientVarsInterface $inputs
312
     * @param PropertySetInterface $ps
313
     *
314
     * @return boolean
315
     *
316
     * @throws InternalWorkflowException
317
     */
318 18
    protected function transitionWorkflow(WorkflowEntryInterface $entry, SplObjectStorage $currentSteps, WorkflowStoreInterface $store, WorkflowDescriptor $wf, ActionDescriptor $action, TransientVarsInterface $transientVars, TransientVarsInterface $inputs, PropertySetInterface $ps)
319
    {
320
        try {
321 18
            $step = $this->getCurrentStep($wf, $action->getId(), $currentSteps, $transientVars, $ps);
322
323 18
            $validators = $action->getValidators();
324 18
            if ($validators->count() > 0) {
325 3
                $this->verifyInputs($validators, $transientVars, $ps);
326 2
            }
327
328
329 17
            if (null !== $step) {
330 6
                $stepPostFunctions = $wf->getStep($step->getStepId())->getPostFunctions();
331 6
                foreach ($stepPostFunctions as $function) {
332 1
                    $this->executeFunction($function, $transientVars, $ps);
333 6
                }
334 6
            }
335
336 17
            $preFunctions = $action->getPreFunctions();
337 17
            foreach ($preFunctions as $preFunction) {
338 1
                $this->executeFunction($preFunction, $transientVars, $ps);
339 17
            }
340
341 17
            $conditionalResults = $action->getConditionalResults();
342 17
            $extraPreFunctions = null;
343 17
            $extraPostFunctions = null;
344
345 17
            $theResult = null;
346
347
348 17
            $currentStepId = null !== $step ? $step->getStepId()  : -1;
349 17
            foreach ($conditionalResults as $conditionalResult) {
350 6
                if ($this->passesConditionsWithType(null, $conditionalResult->getConditions(), $transientVars, $ps, $currentStepId)) {
351 4
                    $theResult = $conditionalResult;
352
353 4
                    $validatorsStorage = $conditionalResult->getValidators();
354 4
                    if ($validatorsStorage->count() > 0) {
355 1
                        $this->verifyInputs($validatorsStorage, $transientVars, $ps);
356
                    }
357
358 3
                    $extraPreFunctions = $conditionalResult->getPreFunctions();
359 3
                    $extraPostFunctions = $conditionalResult->getPostFunctions();
360
361 3
                    break;
362
                }
363 17
            }
364
365
366 16
            if (null ===  $theResult) {
367 13
                $theResult = $action->getUnconditionalResult();
368 13
                $this->verifyInputs($theResult->getValidators(), $transientVars, $ps);
369 13
                $extraPreFunctions = $theResult->getPreFunctions();
370 13
                $extraPostFunctions = $theResult->getPostFunctions();
371 13
            }
372
373 16
            $logMsg = sprintf('theResult=%s %s', $theResult->getStep(), $theResult->getStatus());
374 16
            $this->getLog()->debug($logMsg);
375
376
377 16
            if ($extraPreFunctions && $extraPreFunctions->count() > 0) {
378 2
                foreach ($extraPreFunctions as $function) {
379 2
                    $this->executeFunction($function, $transientVars, $ps);
380 2
                }
381 2
            }
382
383 16
            $split = $theResult->getSplit();
384 16
            $join = $theResult->getJoin();
385 16
            if (null !== $split && 0 !== $split) {
386
                $splitDesc = $wf->getSplit($split);
387
                $results = $splitDesc->getResults();
388
                $splitPreFunctions = [];
389
                $splitPostFunctions = [];
390
391
                foreach ($results as $resultDescriptor) {
392
                    if ($resultDescriptor->getValidators()->count() > 0) {
393
                        $this->verifyInputs($resultDescriptor->getValidators(), $transientVars, $ps);
394
                    }
395
396
                    foreach ($resultDescriptor->getPreFunctions() as $function) {
397
                        $splitPreFunctions[] = $function;
398
                    }
399
                    foreach ($resultDescriptor->getPostFunctions() as $function) {
400
                        $splitPostFunctions[] = $function;
401
                    }
402
                }
403
404
                foreach ($splitPreFunctions as $function) {
405
                    $this->executeFunction($function, $transientVars, $ps);
406
                }
407
408
                if (!$action->isFinish()) {
409
                    $moveFirst = true;
410
411
                    //???????????????????
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...
412
//                $theResults = [];
413
//                foreach ($results as $result) {
414
//                    $theResults[] = $result;
415
//                }
416
417
                    foreach ($results as $resultDescriptor) {
418
                        $moveToHistoryStep = null;
419
420
                        if ($moveFirst) {
421
                            $moveToHistoryStep = $step;
422
                        }
423
424
                        $previousIds = [];
425
426
                        if (null !== $step) {
427
                            $previousIds[] = $step->getStepId();
428
                        }
429
430
                        $this->createNewCurrentStep($resultDescriptor, $entry, $store, $action->getId(), $moveToHistoryStep, $previousIds, $transientVars, $ps);
431
                        $moveFirst = false;
432
                    }
433
                }
434
435
436
                foreach ($splitPostFunctions as $function) {
437
                    $this->executeFunction($function, $transientVars, $ps);
438
                }
439 16
            } elseif (null !== $join && 0 !== $join) {
440
                $joinDesc = $wf->getJoin($join);
441
                $oldStatus = $theResult->getOldStatus();
442
                $caller = $this->context->getCaller();
443
                if (null !== $step) {
444
                    $step = $store->markFinished($step, $action->getId(), new DateTime(), $oldStatus, $caller);
445
                } else {
446
                    $errMsg = 'Invalid step';
447
                    throw new InternalWorkflowException($errMsg);
448
                }
449
450
451
                $store->moveToHistory($step);
452
453
                /** @var StepInterface[] $joinSteps */
454
                $joinSteps = [];
455
                $joinSteps[] = $step;
456
457
                $joinSteps = $this->buildJoinsSteps($currentSteps, $step, $wf, $join, $joinSteps);
458
459
                $historySteps = $store->findHistorySteps($entry->getId());
460
461
                $joinSteps = $this->buildJoinsSteps($historySteps, $step, $wf, $join, $joinSteps);
462
463
464
                $jn = new JoinNodes($joinSteps);
465
                $transientVars['jn'] = $jn;
466
467
468
                if ($this->passesConditionsWithType(null, $joinDesc->getConditions(), $transientVars, $ps, 0)) {
469
                    $joinResult = $joinDesc->getResult();
470
471
                    $joinResultValidators = $joinResult->getValidators();
472
                    if ($joinResultValidators->count() > 0) {
473
                        $this->verifyInputs($joinResultValidators, $transientVars, $ps);
474
                    }
475
476
                    foreach ($joinResult->getPreFunctions() as $function) {
477
                        $this->executeFunction($function, $transientVars, $ps);
478
                    }
479
480
                    $previousIds = [];
481
                    $i = 1;
482
483
                    foreach ($joinSteps as  $currentJoinStep) {
484
                        if (!$historySteps->contains($currentJoinStep) && $currentJoinStep->getId() !== $step->getId()) {
485
                            $store->moveToHistory($step);
486
                        }
487
488 16
                        $previousIds[$i] = $currentJoinStep->getId();
489
                    }
490
491
                    if (!$action->isFinish()) {
492
                        $previousIds[0] = $step->getId();
493
                        $theResult = $joinDesc->getResult();
494
495
                        $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), null, $previousIds, $transientVars, $ps);
496
                    }
497
498
                    foreach ($joinResult->getPostFunctions() as $function) {
499
                        $this->executeFunction($function, $transientVars, $ps);
500
                    }
501
                }
502
            } else {
503 16
                $previousIds = [];
504
505 16
                if (null !== $step) {
506 5
                    $previousIds[] = $step->getId();
507 5
                }
508
509 16
                if (!$action->isFinish()) {
510 16
                    $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), $step, $previousIds, $transientVars, $ps);
511 16
                }
512
            }
513
514 16
            if ($extraPostFunctions && $extraPostFunctions->count() > 0) {
515 2
                foreach ($extraPostFunctions as $function) {
516 2
                    $this->executeFunction($function, $transientVars, $ps);
517 2
                }
518 2
            }
519
520 16
            if (WorkflowEntryInterface::COMPLETED !== $entry->getState() && null !== $wf->getInitialAction($action->getId())) {
521 16
                $this->changeEntryState($entry->getId(), WorkflowEntryInterface::ACTIVATED);
522 16
            }
523
524 16
            if ($action->isFinish()) {
525
                $this->completeEntry($action, $entry->getId(), $this->getCurrentSteps($entry->getId()), WorkflowEntryInterface::COMPLETED);
526
                return true;
527
            }
528
529 16
            $availableAutoActions = $this->getAvailableAutoActions($entry->getId(), $inputs);
530
531 16
            if (count($availableAutoActions) > 0) {
532
                $this->doAction($entry->getId(), $availableAutoActions[0], $inputs);
533
            }
534
535 16
            return false;
536 3
        } catch (\Exception $e) {
537 3
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
538
        }
539
    }
540
541
    /**
542
     * Подготавливает данные о шагах используемых в объеденение
543
     *
544
     * @param StepInterface[]|SplObjectStorage    $steps
545
     * @param StepInterface      $step
546
     * @param WorkflowDescriptor $wf
547
     * @param integer            $join
548
     *
549
     * @param array              $joinSteps
550
     *
551
     * @return array
552
     *
553
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
554
     */
555
    protected function buildJoinsSteps($steps, StepInterface $step, WorkflowDescriptor $wf, $join, array $joinSteps = [])
556
    {
557
        foreach ($steps as $currentStep) {
558
            if ($currentStep->getId() !== $step->getId()) {
559
                $stepDesc = $wf->getStep($currentStep->getStepId());
560
561
                if ($stepDesc->resultsInJoin($join)) {
562
                    $joinSteps[] = $currentStep;
563
                }
564
            }
565
        }
566
567
        return $joinSteps;
568
    }
569
570
    /**
571
     * @param       $id
572
     * @param TransientVarsInterface $inputs
573
     *
574
     * @return array
575
     */
576 19
    protected function getAvailableAutoActions($id, TransientVarsInterface $inputs)
577
    {
578
        try {
579 16
            $store = $this->getPersistence();
580 16
            $entry = $store->findEntry($id);
581
582 16
            if (null === $entry) {
583
                $errMsg = sprintf(
584
                    'Нет сущности workflow c id %s',
585
                    $id
586
                );
587
                throw new InvalidArgumentException($errMsg);
588 19
            }
589
590
591 16
            if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
592
                $logMsg = sprintf('--> состояние %s', $entry->getState());
593
                $this->getLog()->debug($logMsg);
594
                return [0];
595
            }
596
597 16
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
598
599 16
            $l = [];
600 16
            $ps = $store->getPropertySet($id);
601 16
            $transientVars = $inputs;
602 16
            $currentSteps = $store->findCurrentSteps($id);
603
604 16
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
605
606 16
            $globalActions = $wf->getGlobalActions();
607
608 16
            $l = $this->buildListIdsAvailableActions($globalActions, $transientVars, $ps, $l);
609
610 16 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...
611 16
                $availableAutoActionsForStep = $this->getAvailableAutoActionsForStep($wf, $step, $transientVars, $ps);
612
                foreach ($availableAutoActionsForStep as $v) {
613
                    $l[] = $v;
614
                }
615
            }
616
617
            $l = array_unique($l);
618
619
            return $l;
620 16
        } catch (\Exception $e) {
621 16
            $errMsg = 'Ошибка при проверке доступных действий';
622 16
            $this->getLog()->error($errMsg, [$e]);
623
        }
624
625 16
        return [];
626
    }
627
628
    /**
629
     * @param $id
630
     * @param $inputs
631
     *
632
     * @return array
633
     *
634
     */
635
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
636
    {
637
        try {
638
            $store = $this->getPersistence();
639
            $entry = $store->findEntry($id);
640
641
            if (null === $entry) {
642
                $errMsg = sprintf(
643
                    'Не существует экземпляра workflow c id %s',
644
                    $id
645
                );
646
                throw new InvalidArgumentException($errMsg);
647
            }
648
649
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
650
                return [];
651
            }
652
653
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
654
655
            $l = [];
656
            $ps = $store->getPropertySet($id);
657
658
            $transientVars = $inputs;
659
            if (null === $transientVars) {
660
                $transientVars = $this->transientVarsFactory();
661
            }
662
663
            $currentSteps = $store->findCurrentSteps($id);
664
665
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
666
667
            $globalActions = $wf->getGlobalActions();
668
669
            foreach ($globalActions as $action) {
670
                $restriction = $action->getRestriction();
671
                $conditions = null;
672
673
                $transientVars['actionId'] = $action->getId();
674
675
                if (null !== $restriction) {
676
                    $conditions = $restriction->getConditionsDescriptor();
677
                }
678
679
                $flag = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
680
                if ($flag) {
681
                    $l[] = $action->getId();
682
                }
683
            }
684
685
686 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...
687
                $data = $this->getAvailableActionsForStep($wf, $step, $transientVars, $ps);
688
                foreach ($data as $v) {
689
                    $l[] = $v;
690
                }
691
            }
692
            return array_unique($l);
693
        } catch (\Exception $e) {
694
            $errMsg = 'Ошибка проверки доступных действий';
695
            $this->getLog()->error($errMsg, [$e]);
696
        }
697
698
        return [];
699
    }
700
701
    /**
702
     * Подготавливает список id действий в workflow
703
     *
704
     * @param ActionDescriptor[]|SplObjectStorage     $actions
705
     * @param TransientVarsInterface $transientVars
706
     * @param PropertySetInterface   $ps
707
     * @param array                  $storage
708
     *
709
     * @return array
710
     *
711
     * @throws InternalWorkflowException
712
     * @throws WorkflowException
713
     */
714 16
    protected function buildListIdsAvailableActions($actions, TransientVarsInterface $transientVars, PropertySetInterface $ps, array $storage = [])
715
    {
716 16
        foreach ($actions as $action) {
717 16
            if ($action instanceof ActionDescriptor) {
718 16
                $errMsg = sprintf('Invalid workflow action. Action not implement %s', ActionDescriptor::class);
719 16
                throw new InternalWorkflowException($errMsg);
720
            }
721
            $transientVars['actionId'] = $action->getId();
722
723
            if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
724
                $storage[] = $action->getId();
725
            }
726 16
        }
727
728 16
        return $storage;
729
    }
730
731
    /**
732
     * @param WorkflowDescriptor   $wf
733
     * @param StepInterface        $step
734
     * @param TransientVarsInterface                $transientVars
735
     * @param PropertySetInterface $ps
736
     *
737
     * @return array
738
     *
739
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
740
     * @throws InternalWorkflowException
741
     * @throws WorkflowException
742
     */
743 16
    protected function getAvailableAutoActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
744
    {
745 16
        $l = [];
746 16
        $s = $wf->getStep($step->getStepId());
747
748 16
        if (null === $s) {
749
            $msg = sprintf('getAvailableAutoActionsForStep вызвана с несуществующим id %s', $step->getStepId());
750
            $this->getLog()->debug($msg);
751
            return $l;
752
        }
753
754
755 16
        $actions = $s->getActions();
756 16
        if (null === $actions || 0 === $actions->count()) {
757
            return $l;
758
        }
759
760 16
        $l = $this->buildListIdsAvailableActions($actions, $transientVars, $ps, $l);
761
762
        return $l;
763
    }
764
765
    /**
766
     * @param ActionDescriptor $action
767
     * @param                  $id
768
     * @param array|Traversable $currentSteps
769
     * @param                  $state
770
     *
771
     * @return void
772
     *
773
     * @throws InvalidArgumentException
774
     * @throws InternalWorkflowException
775
     */
776
    protected function completeEntry(ActionDescriptor $action = null, $id, $currentSteps, $state)
777
    {
778
        $this->validateIterateData($currentSteps);
779
780
781
        $this->getPersistence()->setEntryState($id, $state);
782
783
        $oldStatus = null !== $action ? $action->getUnconditionalResult()->getOldStatus() : 'Finished';
784
        $actionIdValue = null !== $action ? $action->getId() : -1;
785
        foreach ($currentSteps as $step) {
786
            $this->getPersistence()->markFinished($step, $actionIdValue, new DateTime(), $oldStatus, $this->context->getCaller());
787
            $this->getPersistence()->moveToHistory($step);
788
        }
789
    }
790
    /**
791
     * @param ResultDescriptor       $theResult
792
     * @param WorkflowEntryInterface $entry
793
     * @param WorkflowStoreInterface $store
794
     * @param integer                $actionId
795
     * @param StepInterface          $currentStep
796
     * @param array                  $previousIds
797
     * @param TransientVarsInterface                  $transientVars
798
     * @param PropertySetInterface   $ps
799
     *
800
     * @return StepInterface
801
     *
802
     * @throws InternalWorkflowException
803
     * @throws StoreException
804
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
805
     * @throws WorkflowException
806
     */
807 16
    protected function createNewCurrentStep(
808
        ResultDescriptor $theResult,
809
        WorkflowEntryInterface $entry,
810
        WorkflowStoreInterface $store,
811
        $actionId,
812
        StepInterface $currentStep = null,
813
        array $previousIds = [],
814
        TransientVarsInterface $transientVars,
815
        PropertySetInterface $ps
816
    ) {
817
        try {
818 16
            $nextStep = $theResult->getStep();
819
820 16
            if (-1 === $nextStep) {
821
                if (null !== $currentStep) {
822
                    $nextStep = $currentStep->getStepId();
823
                } else {
824
                    $errMsg = 'Неверный аргумент. Новый шаг является таким же как текущий. Но текущий шаг не указан';
825
                    throw new StoreException($errMsg);
826
                }
827
            }
828
829 16
            $owner = $theResult->getOwner();
830
831 16
            $logMsg = sprintf(
832 16
                'Результат: stepId=%s, status=%s, owner=%s, actionId=%s, currentStep=%s',
833 16
                $nextStep,
834 16
                $theResult->getStatus(),
835 16
                $owner,
836 16
                $actionId,
837 16
                null !== $currentStep ? $currentStep->getId() : 0
838 16
            );
839 16
            $this->getLog()->debug($logMsg);
840
841 16
            $variableResolver = $this->getConfiguration()->getVariableResolver();
842
843 16
            if (null !== $owner) {
844
                $o = $variableResolver->translateVariables($owner, $transientVars, $ps);
845
                $owner = null !== $o ? (string)$o : null;
846
            }
847
848
849 16
            $oldStatus = $theResult->getOldStatus();
850 16
            $oldStatus = (string)$variableResolver->translateVariables($oldStatus, $transientVars, $ps);
851
852 16
            $status = $theResult->getStatus();
853 16
            $status = (string)$variableResolver->translateVariables($status, $transientVars, $ps);
854
855
856 16
            if (null !== $currentStep) {
857 5
                $store->markFinished($currentStep, $actionId, new DateTime(), $oldStatus, $this->context->getCaller());
858 5
                $store->moveToHistory($currentStep);
859 5
            }
860
861 16
            $startDate = new DateTime();
862 16
            $dueDate = null;
863
864 16
            $theResultDueDate = (string)$theResult->getDueDate();
865 16
            $theResultDueDate = trim($theResultDueDate);
866 16
            if (strlen($theResultDueDate) > 0) {
867
                $dueDateObject = $variableResolver->translateVariables($theResultDueDate, $transientVars, $ps);
868
869
                if ($dueDateObject instanceof DateTime) {
870
                    $dueDate = $dueDateObject;
871
                } elseif (is_string($dueDateObject)) {
872
                    $dueDate = new DateTime($dueDate);
873
                } elseif (is_numeric($dueDateObject)) {
874
                    $dueDate = DateTime::createFromFormat('U', $dueDateObject);
875
                    if (false === $dueDate) {
876
                        $errMsg = 'Invalid due date conversion';
877
                        throw new Exception\InternalWorkflowException($errMsg);
878
                    }
879
                }
880
            }
881
882 16
            $newStep = $store->createCurrentStep($entry->getId(), $nextStep, $owner, $startDate, $dueDate, $status, $previousIds);
883 16
            $transientVars['createdStep'] =  $newStep;
884
885 16
            if (null === $currentStep && 0 === count($previousIds)) {
886 16
                $currentSteps = [];
887 16
                $currentSteps[] = $newStep;
888 16
                $transientVars['currentSteps'] =  $currentSteps;
889 16
            }
890
891 16
            if (! $transientVars->offsetExists('descriptor')) {
892
                $errMsg = 'Ошибка при получение дескриптора workflow из transientVars';
893
                throw new InternalWorkflowException($errMsg);
894
            }
895
896
            /** @var WorkflowDescriptor $descriptor */
897 16
            $descriptor = $transientVars['descriptor'];
898 16
            $step = $descriptor->getStep($nextStep);
899
900 16
            if (null === $step) {
901
                $errMsg = sprintf('Шаг #%s не найден', $nextStep);
902
                throw new WorkflowException($errMsg);
903
            }
904
905 16
            $preFunctions = $step->getPreFunctions();
906
907 16
            foreach ($preFunctions as $function) {
908
                $this->executeFunction($function, $transientVars, $ps);
909 16
            }
910 16
        } catch (WorkflowException $e) {
911
            $this->context->setRollbackOnly();
912
            /** @var WorkflowException $e */
913
            throw $e;
914
        }
915 16
    }
916
917
    /**
918
     * Создает хранилище переменных
919
     *
920
     * @param $class
921
     *
922
     * @return TransientVarsInterface
923
     */
924
    protected function transientVarsFactory($class = BaseTransientVars::class)
925
    {
926
        $r = new \ReflectionClass($class);
927
        return $r->newInstance();
928
    }
929
930
    /**
931
     *
932
     *
933
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
934
     *
935
     * @param integer $entryId id запущенного процесса workflow
936
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
937
     * @param TransientVarsInterface $inputs Входные данные для перехода
938
     *
939
     * @return void
940
     *
941
     * @throws WorkflowException
942
     * @throws InvalidActionException
943
     * @throws InvalidArgumentException
944
     * @throws InternalWorkflowException
945
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
946
     */
947 8
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
948
    {
949 8
        $actionId = (integer)$actionId;
950 8
        if (null === $inputs) {
951
            $inputs = $this->transientVarsFactory();
952
        }
953 8
        $transientVars = $inputs;
954 8
        $inputs = clone $transientVars;
955
956 8
        $store = $this->getPersistence();
957 8
        $entry = $store->findEntry($entryId);
958
959 8
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
960
            return;
961
        }
962
963 8
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
964
965 8
        $currentSteps = $store->findCurrentSteps($entryId);
966 8
        $action = null;
967
968 8
        $ps = $store->getPropertySet($entryId);
969
970 8
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
971
972
973 8
        $validAction = false;
974
975 8
        foreach ($wf->getGlobalActions() as $actionDesc) {
976
            if ($actionId === $actionDesc->getId()) {
977
                $action = $actionDesc;
978
979
                if ($this->isActionAvailable($action, $transientVars, $ps, 0)) {
980
                    $validAction = true;
981
                }
982
            }
983 8
        }
984
985
986 8
        foreach ($currentSteps as $step) {
987 8
            $s = $wf->getStep($step->getStepId());
988
989 8
            foreach ($s->getActions() as $actionDesc) {
990 8
                if (!$actionDesc instanceof ActionDescriptor) {
991
                    $errMsg = 'Invalid action descriptor';
992
                    throw new InternalWorkflowException($errMsg);
993
                }
994
995 8
                if ($actionId === $actionDesc->getId()) {
996 8
                    $action = $actionDesc;
997
998 8
                    if ($this->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
999 6
                        $validAction = true;
1000 6
                    }
1001 8
                }
1002 8
            }
1003 8
        }
1004
1005
1006 8
        if (!$validAction) {
1007 2
            $errMsg = sprintf(
1008 2
                'Action %s is invalid',
1009
                $actionId
1010 2
            );
1011 2
            throw new InvalidActionException($errMsg);
1012
        }
1013
1014
1015
        try {
1016 6
            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...
1017
                $this->checkImplicitFinish($action, $entryId);
1018
            }
1019 6
        } catch (WorkflowException $e) {
1020
            $this->context->setRollbackOnly();
1021
            /** @var  WorkflowException $e*/
1022
            throw $e;
1023
        }
1024 5
    }
1025
1026
    /**
1027
     * @param ActionDescriptor $action
1028
     * @param                  $id
1029
     *
1030
     * @return void
1031
     *
1032
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1033
     * @throws InvalidArgumentException
1034
     * @throws InternalWorkflowException
1035
     */
1036
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
1037
    {
1038
        $store = $this->getPersistence();
1039
        $entry = $store->findEntry($id);
1040
1041
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1042
1043
        $currentSteps = $store->findCurrentSteps($id);
1044
1045
        $isCompleted = $wf->getGlobalActions()->count() === 0;
1046
1047
        foreach ($currentSteps as $step) {
1048
            if ($isCompleted) {
1049
                break;
1050
            }
1051
1052
            $stepDes = $wf->getStep($step->getStepId());
1053
1054
            if ($stepDes->getActions()->count() > 0) {
1055
                $isCompleted = true;
1056
            }
1057
        }
1058
1059
        if ($isCompleted) {
1060
            $this->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
1061
        }
1062
    }
1063
1064
    /**
1065
     *
1066
     * Check if the state of the specified workflow instance can be changed to the new specified one.
1067
     *
1068
     * @param integer $id The workflow instance id.
1069
     * @param integer $newState The new state id.
1070
     *
1071
     * @return boolean true if the state of the workflow can be modified, false otherwise.
1072
     *
1073
     * @throws InternalWorkflowException
1074
     */
1075 16
    public function canModifyEntryState($id, $newState)
1076
    {
1077 16
        $store = $this->getPersistence();
1078 16
        $entry = $store->findEntry($id);
1079
1080 16
        $currentState = $entry->getState();
1081
1082 16
        $result = false;
1083
1084
1085
        try {
1086
            switch ($newState) {
1087 16
                case WorkflowEntryInterface::COMPLETED: {
1088
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
1089
                        $result = true;
1090
                    }
1091
                    break;
1092
                }
1093
1094
                //@TODO Разобраться с бизнес логикой. Может быть нужно добавить break
1095
                /** @noinspection PhpMissingBreakStatementInspection */
1096 16
                case WorkflowEntryInterface::CREATED: {
1097
                    $result = false;
1098
                }
1099 16 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...
1100 16
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
1101 16
                        $result = true;
1102 16
                    }
1103 16
                    break;
1104
                }
1105
                case WorkflowEntryInterface::SUSPENDED: {
1106
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
1107
                        $result = true;
1108
                    }
1109
                    break;
1110
                }
1111 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...
1112
                    if (WorkflowEntryInterface::SUSPENDED === $currentState || WorkflowEntryInterface::ACTIVATED === $currentState || WorkflowEntryInterface::CREATED === $currentState) {
1113
                        $result = true;
1114
                    }
1115
                    break;
1116
                }
1117
                default: {
1118
                    $result = false;
1119
                    break;
1120
                }
1121
1122
            }
1123
1124 16
            return $result;
1125
        } 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...
1126
            $errMsg = sprintf(
1127
                'Ошибка проверки изменения состояния для инстанса #%s',
1128
                $id
1129
            );
1130
            $this->getLog()->error($errMsg, [$e]);
1131
        }
1132
1133
        return false;
1134
    }
1135
1136
1137
    /**
1138
     *
1139
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
1140
     *
1141
     * @param integer $entryId id экземпляра workflow
1142
     *
1143
     * @return SplObjectStorage|StepInterface[]
1144
     *
1145
     * @throws InternalWorkflowException
1146
     */
1147
    public function getCurrentSteps($entryId)
1148
    {
1149
        return $this->getStepFromStorage($entryId, static::CURRENT_STEPS);
1150
    }
1151
1152
    /**
1153
     * Возвращает информацию о том в какие шаги, были осуществленны переходы, для процесса workflow с заданным id
1154
     *
1155
     * @param integer $entryId уникальный идентификатор процесса workflow
1156
     *
1157
     * @return StepInterface[]|SplObjectStorage список шагов
1158
     *
1159
     * @throws InternalWorkflowException
1160
     */
1161
    public function getHistorySteps($entryId)
1162
    {
1163
        return $this->getStepFromStorage($entryId, static::HISTORY_STEPS);
1164
    }
1165
1166
    /**
1167
     * Получение шагов информации о шагах процесса workflow
1168
     *
1169
     * @param $entryId
1170
     * @param $type
1171
     *
1172
     * @return Spi\StepInterface[]|SplObjectStorage
1173
     *
1174
     * @throws InternalWorkflowException
1175
     */
1176
    protected function getStepFromStorage($entryId, $type)
1177
    {
1178
        try {
1179
            $store = $this->getPersistence();
1180
1181
            if (static::CURRENT_STEPS === $type) {
1182
                return $store->findCurrentSteps($entryId);
1183
            } elseif (static::HISTORY_STEPS === $type) {
1184
                return $store->findHistorySteps($entryId);
1185
            }
1186
        } catch (StoreException $e) {
1187
            $errMsg = sprintf(
1188
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
1189
                $entryId
1190
            );
1191
            $this->getLog()->error($errMsg, [$e]);
1192
        }
1193
1194
        return new SplObjectStorage();
1195
    }
1196
1197
1198
1199
    /**
1200
     *
1201
     *
1202
     * Modify the state of the specified workflow instance.
1203
     * @param integer $id The workflow instance id.
1204
     * @param integer $newState the new state to change the workflow instance to.
1205
     *
1206
     * @throws InvalidArgumentException
1207
     * @throws InvalidEntryStateException
1208
     * @throws InternalWorkflowException
1209
     */
1210 16
    public function changeEntryState($id, $newState)
1211
    {
1212 16
        $store = $this->getPersistence();
1213 16
        $entry = $store->findEntry($id);
1214
1215 16
        if ($newState === $entry->getState()) {
1216
            return;
1217
        }
1218
1219 16
        if ($this->canModifyEntryState($id, $newState)) {
1220 16
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
1221
                $currentSteps = $this->getCurrentSteps($id);
1222
1223
                if (count($currentSteps) > 0) {
1224
                    $this->completeEntry(null, $id, $currentSteps, $newState);
1225
                }
1226
            }
1227
1228 16
            $store->setEntryState($id, $newState);
1229 16
        } else {
1230
            $errMsg = sprintf(
1231
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
1232
                $id,
1233
                $entry->getState(),
1234
                $newState
1235
            );
1236
1237
            throw new InvalidEntryStateException($errMsg);
1238
        }
1239
1240 16
        $msg = sprintf(
1241 16
            '%s : Новое состояние: %s',
1242 16
            $entry->getId(),
1243 16
            $entry->getState()
1244 16
        );
1245 16
        $this->getLog()->debug($msg);
1246 16
    }
1247
1248
1249
    /**
1250
     * @param FunctionDescriptor $function
1251
     * @param TransientVarsInterface $transientVars
1252
     * @param PropertySetInterface $ps
1253
     *
1254
     * @throws WorkflowException
1255
     * @throws InternalWorkflowException
1256
     */
1257 4
    protected function executeFunction(FunctionDescriptor $function, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1258
    {
1259 4
        if (null !== $function) {
1260 4
            $type = $function->getType();
1261
1262 4
            $argsOriginal = $function->getArgs();
1263 4
            $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
1264
1265 4
            $provider = $this->getResolver()->getFunction($type, $args);
1266
1267 4
            if (null === $provider) {
1268
                $this->context->setRollbackOnly();
1269
                $errMsg = 'Не загружен провайдер для функции';
1270
                throw new WorkflowException($errMsg);
1271
            }
1272
1273
            try {
1274 4
                $provider->execute($transientVars, $args, $ps);
1275 4
            } catch (WorkflowException $e) {
1276
                $this->context->setRollbackOnly();
1277
                /** @var  WorkflowException $e*/
1278
                throw $e;
1279
            }
1280 4
        }
1281 4
    }
1282
1283
1284
    /**
1285
     * @param $validatorsStorage
1286
     * @param TransientVarsInterface $transientVars
1287
     * @param PropertySetInterface $ps
1288
     *
1289
     * @throws WorkflowException
1290
     * @throws InvalidArgumentException
1291
     * @throws InternalWorkflowException
1292
     * @throws InvalidInputException
1293
     */
1294 15
    protected function verifyInputs($validatorsStorage, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1295
    {
1296 15
        $validators = $this->convertDataInArray($validatorsStorage);
1297
1298
        /** @var ValidatorDescriptor[] $validators */
1299 15
        foreach ($validators as $input) {
1300 6
            if (null !== $input) {
1301 6
                $type = $input->getType();
1302 6
                $argsOriginal = $input->getArgs();
1303
1304 6
                $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
1305
1306
1307 6
                $validator = $this->getResolver()->getValidator($type, $args);
1308
1309 6
                if (null === $validator) {
1310
                    $this->context->setRollbackOnly();
1311
                    $errMsg = 'Ошибка при загрузке валидатора';
1312
                    throw new WorkflowException($errMsg);
1313
                }
1314
1315
                try {
1316 6
                    $validator->validate($transientVars, $args, $ps);
1317 6
                } catch (InvalidInputException $e) {
1318
                    /** @var  InvalidInputException $e*/
1319
                    throw $e;
1320 2
                } catch (\Exception $e) {
1321 2
                    $this->context->setRollbackOnly();
1322
1323 2
                    if ($e instanceof WorkflowException) {
1324
                        /** @var  WorkflowException $e*/
1325
                        throw $e;
1326
                    }
1327
1328 2
                    throw new WorkflowException($e->getMessage(), $e->getCode(), $e);
1329
                }
1330 4
            }
1331 13
        }
1332 13
    }
1333
1334
1335
    /**
1336
     * Возвращает текущий шаг
1337
     *
1338
     * @param WorkflowDescriptor $wfDesc
1339
     * @param integer $actionId
1340
     * @param StepInterface[]|SplObjectStorage $currentSteps
1341
     * @param TransientVarsInterface $transientVars
1342
     * @param PropertySetInterface $ps
1343
     *
1344
     * @return StepInterface
1345
     *
1346
     * @throws InternalWorkflowException
1347
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1348
     * @throws WorkflowException
1349
     */
1350 18
    protected function getCurrentStep(WorkflowDescriptor $wfDesc, $actionId, SplObjectStorage $currentSteps, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1351
    {
1352 18
        if (1 === $currentSteps->count()) {
1353 6
            $currentSteps->rewind();
1354 6
            return $currentSteps->current();
1355
        }
1356
1357
1358 18
        foreach ($currentSteps as $step) {
1359
            $stepId = $step->getId();
1360
            $action = $wfDesc->getStep($stepId)->getAction($actionId);
1361
1362
            if ($this->isActionAvailable($action, $transientVars, $ps, $stepId)) {
1363
                return $step;
1364
            }
1365 18
        }
1366
1367 18
        return null;
1368
    }
1369
1370
    /**
1371
     * @param ActionDescriptor|null $action
1372
     * @param TransientVarsInterface $transientVars
1373
     * @param PropertySetInterface $ps
1374
     * @param $stepId
1375
     *
1376
     * @return boolean
1377
     *
1378
     * @throws InternalWorkflowException
1379
     * @throws WorkflowException
1380
     */
1381 8
    protected function isActionAvailable(ActionDescriptor $action = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $stepId)
1382
    {
1383 8
        if (null === $action) {
1384
            return false;
1385
        }
1386
1387 8
        $result = null;
1388 8
        $actionHash = spl_object_hash($action);
1389
1390 8
        $result = array_key_exists($actionHash, $this->stateCache) ? $this->stateCache[$actionHash] : $result;
1391
1392 8
        $wf = $this->getWorkflowDescriptorForAction($action);
1393
1394
1395 8
        if (null === $result) {
1396 8
            $restriction = $action->getRestriction();
1397 8
            $conditions = null;
1398
1399 8
            if (null !== $restriction) {
1400 5
                $conditions = $restriction->getConditionsDescriptor();
1401 5
            }
1402
1403 8
            $result = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $stepId)
1404 8
                && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $stepId);
1405
1406 8
            $this->stateCache[$actionHash] = $result;
1407 8
        }
1408
1409
1410 8
        $result = (boolean)$result;
1411
1412 8
        return $result;
1413
    }
1414
1415
    /**
1416
     * По дейсвтию получаем дексрипторв workflow
1417
     *
1418
     * @param ActionDescriptor $action
1419
     *
1420
     * @return WorkflowDescriptor
1421
     *
1422
     * @throws InternalWorkflowException
1423
     */
1424 8
    private function getWorkflowDescriptorForAction(ActionDescriptor $action)
1425
    {
1426 8
        $objWfd = $action;
1427
1428 8
        $count = 0;
1429 8
        while (!$objWfd instanceof WorkflowDescriptor || null === $objWfd) {
1430 8
            $objWfd = $objWfd->getParent();
1431
1432 8
            $count++;
1433 8
            if ($count > 10) {
1434
                $errMsg = 'Ошибка при получение WorkflowDescriptor';
1435
                throw new InternalWorkflowException($errMsg);
1436
            }
1437 8
        }
1438
1439 8
        return $objWfd;
1440
    }
1441
1442
1443
    /**
1444
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1445
     *
1446
     * @param string $workflowName имя workflow
1447
     * @param integer $initialAction id начального состояния
1448
     * @param TransientVarsInterface $inputs
1449
     *
1450
     * @return bool
1451
     *
1452
     * @throws InvalidArgumentException
1453
     * @throws WorkflowException
1454
     * @throws InternalWorkflowException
1455
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1456
     */
1457
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
1458
    {
1459
        $mockWorkflowName = $workflowName;
1460
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
1461
1462
        try {
1463
            $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...
1464
            if (!$ps instanceof PropertySetInterface) {
1465
                $errMsg = 'Invalid create PropertySet';
1466
                throw new InternalWorkflowException($errMsg);
1467
            }
1468
        } catch (\Exception $e) {
1469
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
1470
        }
1471
1472
1473
1474
1475
        if (null === $inputs) {
1476
            $inputs = $this->transientVarsFactory();
1477
        }
1478
        $transientVars = $inputs;
1479
1480
        try {
1481
            $this->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
1482
1483
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
1484
1485
            return $result;
1486
        } catch (InvalidActionException $e) {
1487
            $this->getLog()->error($e->getMessage(), [$e]);
1488
1489
            return false;
1490
        } catch (WorkflowException $e) {
1491
            $errMsg = sprintf(
1492
                'Ошибка при проверки canInitialize: %s',
1493
                $e->getMessage()
1494
            );
1495
            $this->getLog()->error($errMsg, [$e]);
1496
1497
            return false;
1498
        }
1499
    }
1500
1501
1502
    /**
1503
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1504
     *
1505
     * @param string $workflowName имя workflow
1506
     * @param integer $initialAction id начального состояния
1507
     * @param TransientVarsInterface $transientVars
1508
     *
1509
     * @param PropertySetInterface $ps
1510
     *
1511
     * @return bool
1512
     *
1513
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1514
     * @throws InvalidActionException
1515
     * @throws InternalWorkflowException
1516
     * @throws WorkflowException
1517
     */
1518 19
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1519
    {
1520 19
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
1521
1522 19
        $actionDescriptor = $wf->getInitialAction($initialAction);
1523
1524 19
        if (null === $actionDescriptor) {
1525
            $errMsg = sprintf(
1526
                'Некорректное инициирующие действие # %s',
1527
                $initialAction
1528
            );
1529
            throw new InvalidActionException($errMsg);
1530
        }
1531
1532 19
        $restriction = $actionDescriptor->getRestriction();
1533
1534
1535 19
        $conditions = null;
1536 19
        if (null !== $restriction) {
1537 2
            $conditions = $restriction->getConditionsDescriptor();
1538 2
        }
1539
1540 19
        $passesConditions = $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1541
1542 19
        return $passesConditions;
1543
    }
1544
1545
    /**
1546
     * Возвращает резолвер
1547
     *
1548
     * @return TypeResolverInterface
1549
     */
1550 17
    public function getResolver()
1551
    {
1552 17
        if (null !== $this->typeResolver) {
1553 13
            return $this->typeResolver;
1554
        }
1555
1556 17
        $classResolver = $this->getDefaultTypeResolverClass();
1557 17
        $r = new ReflectionClass($classResolver);
1558 17
        $resolver = $r->newInstance();
1559 17
        $this->typeResolver = $resolver;
1560
1561 17
        return $this->typeResolver;
1562
    }
1563
1564
    /**
1565
     * Возвращает хранилище состояния workflow
1566
     *
1567
     * @return WorkflowStoreInterface
1568
     *
1569
     * @throws InternalWorkflowException
1570
     */
1571 19
    protected function getPersistence()
1572
    {
1573 19
        return $this->getConfiguration()->getWorkflowStore();
1574
    }
1575
1576
    /**
1577
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
1578
     * инициализирует ее.
1579
     *
1580
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
1581
     *
1582
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
1583
     *
1584
     * @throws InternalWorkflowException
1585
     */
1586 19
    public function getConfiguration()
1587
    {
1588 19
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
1589
1590 19
        if (!$config->isInitialized()) {
1591
            try {
1592
                $config->load(null);
1593
            } catch (FactoryException $e) {
1594
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
1595
                $this->getLog()->critical($errMsg, ['exception' => $e]);
1596
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1597
            }
1598
        }
1599
1600 19
        return $config;
1601
    }
1602
1603
    /**
1604
     * @return LoggerInterface
1605
     */
1606 16
    public function getLog()
1607
    {
1608 16
        return $this->log;
1609
    }
1610
1611
    /**
1612
     * @param LoggerInterface $log
1613
     *
1614
     * @return $this
1615
     *
1616
     * @throws InternalWorkflowException
1617
     */
1618
    public function setLog($log)
1619
    {
1620
        try {
1621
            LogFactory::validLogger($log);
1622
        } catch (\Exception $e) {
1623
            $errMsg = 'Ошибка при валидации логера';
1624
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1625
        }
1626
1627
1628
        $this->log = $log;
1629
1630
        return $this;
1631
    }
1632
1633
1634
    /**
1635
     * Get the workflow descriptor for the specified workflow name.
1636
     *
1637
     * @param string $workflowName The workflow name.
1638
     * @return WorkflowDescriptor
1639
     *
1640
     * @throws InternalWorkflowException
1641
     */
1642
    public function getWorkflowDescriptor($workflowName)
1643
    {
1644
        try {
1645
            return $this->getConfiguration()->getWorkflow($workflowName);
1646
        } catch (FactoryException $e) {
1647
            $errMsg = 'Ошибка при загрузке workflow';
1648
            $this->getLog()->error($errMsg, ['exception' => $e]);
1649
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1650
        }
1651
    }
1652
1653
1654
    /**
1655
     * Executes a special trigger-function using the context of the given workflow instance id.
1656
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
1657
     *
1658
     * @param integer $id The workflow instance id
1659
     * @param integer $triggerId The id of the special trigger-function
1660
     *
1661
     * @throws InvalidArgumentException
1662
     * @throws WorkflowException
1663
     * @throws InternalWorkflowException
1664
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1665
     */
1666
    public function executeTriggerFunction($id, $triggerId)
1667
    {
1668
        $store = $this->getPersistence();
1669
        $entry = $store->findEntry($id);
1670
1671
        if (null === $entry) {
1672
            $errMsg = sprintf(
1673
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
1674
                $triggerId,
1675
                $id
1676
            );
1677
            $this->getLog()->warning($errMsg);
1678
            return;
1679
        }
1680
1681
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1682
1683
        $ps = $store->getPropertySet($id);
1684
        $transientVars = $this->transientVarsFactory();
1685
1686
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
1687
1688
        $this->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
1689
    }
1690
1691
1692
    /**
1693
     * @param WorkflowDescriptor   $wf
1694
     * @param StepInterface        $step
1695
     * @param TransientVarsInterface                $transientVars
1696
     * @param PropertySetInterface $ps
1697
     *
1698
     * @return array
1699
     *
1700
     * @throws InternalWorkflowException
1701
     * @throws WorkflowException
1702
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1703
     */
1704
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1705
    {
1706
        $l = [];
1707
        $s = $wf->getStep($step->getStepId());
1708
1709
        if (null === $s) {
1710
            $errMsg = sprintf(
1711
                'getAvailableActionsForStep вызван с не существующим id шага %s',
1712
                $step->getStepId()
1713
            );
1714
1715
            $this->getLog()->warning($errMsg);
1716
1717
            return $l;
1718
        }
1719
1720
        $actions  = $s->getActions();
1721
1722
        if (null === $actions || 0  === $actions->count()) {
1723
            return $l;
1724
        }
1725
1726
        foreach ($actions as $action) {
1727
            $restriction = $action->getRestriction();
1728
            $conditions = null;
1729
1730
            $transientVars['actionId'] = $action->getId();
1731
1732
1733
            if (null !== $restriction) {
1734
                $conditions = $restriction->getConditionsDescriptor();
1735
            }
1736
1737
            $f = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
1738
                 && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
1739
            if ($f) {
1740
                $l[] = $action->getId();
1741
            }
1742
        }
1743
1744
        return $l;
1745
    }
1746
1747
    /**
1748
     * @param ConfigurationInterface $configuration
1749
     *
1750
     * @return $this
1751
     */
1752 19
    public function setConfiguration(ConfigurationInterface $configuration)
1753
    {
1754 19
        $this->configuration = $configuration;
1755
1756 19
        return $this;
1757
    }
1758
1759
    /**
1760
     * Возвращает состояние для текущего экземпляра workflow
1761
     *
1762
     * @param integer $id id экземпляра workflow
1763
     * @return integer id текущего состояния
1764
     *
1765
     * @throws InternalWorkflowException
1766
     */
1767
    public function getEntryState($id)
1768
    {
1769
        try {
1770
            $store = $this->getPersistence();
1771
1772
            return $store->findEntry($id)->getState();
1773
        } catch (StoreException $e) {
1774
            $errMsg = sprintf(
1775
                'Ошибка при получение состояния экземпляра workflow c id# %s',
1776
                $id
1777
            );
1778
            $this->getLog()->error($errMsg, [$e]);
1779
        }
1780
1781
        return WorkflowEntryInterface::UNKNOWN;
1782
    }
1783
1784
1785
    /**
1786
     * Настройки хранилища
1787
     *
1788
     * @return array
1789
     *
1790
     * @throws InternalWorkflowException
1791
     */
1792
    public function getPersistenceProperties()
1793
    {
1794
        return $this->getConfiguration()->getPersistenceArgs();
1795
    }
1796
1797
1798
    /**
1799
     * Get the PropertySet for the specified workflow instance id.
1800
     * @param integer $id The workflow instance id.
1801
     *
1802
     * @return PropertySetInterface
1803
     * @throws InternalWorkflowException
1804
     */
1805
    public function getPropertySet($id)
1806
    {
1807
        $ps = null;
1808
1809
        try {
1810
            $ps = $this->getPersistence()->getPropertySet($id);
1811
        } catch (StoreException $e) {
1812
            $errMsg = sprintf(
1813
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1814
                $id
1815
            );
1816
            $this->getLog()->error($errMsg, [$e]);
1817
        }
1818
1819
        return $ps;
1820
    }
1821
1822
    /**
1823
     * @return string[]
1824
     *
1825
     * @throws InternalWorkflowException
1826
     */
1827
    public function getWorkflowNames()
1828
    {
1829
        try {
1830
            return $this->getConfiguration()->getWorkflowNames();
1831
        } catch (FactoryException $e) {
1832
            $errMsg = 'Ошибка при получение имен workflow';
1833
            $this->getLog()->error($errMsg, [$e]);
1834
        }
1835
1836
        return [];
1837
    }
1838
1839
    /**
1840
     * @param TypeResolverInterface $typeResolver
1841
     *
1842
     * @return $this
1843
     */
1844
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1845
    {
1846
        $this->typeResolver = $typeResolver;
1847
1848
        return $this;
1849
    }
1850
1851
1852
    /**
1853
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1854
     * @param integer $id id the workflow instance id.
1855
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1856
     *
1857
     * @return array  A List of permissions specified currently (a permission is a string name).
1858
     *
1859
     */
1860
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1861
    {
1862
        try {
1863
            $store = $this->getPersistence();
1864
            $entry = $store->findEntry($id);
1865
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1866
1867
            $ps = $store->getPropertySet($id);
1868
1869
            if (null === $inputs) {
1870
                $inputs = $this->transientVarsFactory();
1871
            }
1872
            $transientVars = $inputs;
1873
1874
            $currentSteps = $store->findCurrentSteps($id);
1875
1876
            try {
1877
                $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1878
            } catch (\Exception $e) {
1879
                $errMsg = sprintf(
1880
                    'Внутреннея ошибка: %s',
1881
                    $e->getMessage()
1882
                );
1883
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1884
            }
1885
1886
1887
            $s = [];
1888
1889
            foreach ($currentSteps as $step) {
1890
                $stepId = $step->getStepId();
1891
1892
                $xmlStep = $wf->getStep($stepId);
1893
1894
                $securities = $xmlStep->getPermissions();
1895
1896
                foreach ($securities as $security) {
1897
                    if (!$security instanceof PermissionDescriptor) {
1898
                        $errMsg = 'Invalid PermissionDescriptor';
1899
                        throw new InternalWorkflowException($errMsg);
1900
                    }
1901
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1902
                    if (null !== $security->getRestriction() && $this->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1903
                        $s[$security->getName()] = $security->getName();
1904
                    }
1905
                }
1906
            }
1907
1908
            return $s;
1909
        } catch (\Exception $e) {
1910
            $errMsg = sprintf(
1911
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1912
                $id
1913
            );
1914
            $this->getLog()->error($errMsg, [$e]);
1915
        }
1916
1917
        return [];
1918
    }
1919
1920
1921
    /**
1922
     * Get the name of the specified workflow instance.
1923
     *
1924
     * @param integer $id the workflow instance id.
1925
     *
1926
     * @return string
1927
     *
1928
     * @throws InternalWorkflowException
1929
     */
1930
    public function getWorkflowName($id)
1931
    {
1932
        try {
1933
            $store = $this->getPersistence();
1934
            $entry = $store->findEntry($id);
1935
1936
            if (null !== $entry) {
1937
                return $entry->getWorkflowName();
1938
            }
1939
        } catch (FactoryException $e) {
1940
            $errMsg = sprintf(
1941
                'Ошибка при получение имен workflow для инстанса с id # %s',
1942
                $id
1943
            );
1944
            $this->getLog()->error($errMsg, [$e]);
1945
        }
1946
1947
        return null;
1948
    }
1949
1950
    /**
1951
     * Удаляет workflow
1952
     *
1953
     * @param string $workflowName
1954
     *
1955
     * @return bool
1956
     *
1957
     * @throws InternalWorkflowException
1958
     */
1959
    public function removeWorkflowDescriptor($workflowName)
1960
    {
1961
        return $this->getConfiguration()->removeWorkflow($workflowName);
1962
    }
1963
1964
    /**
1965
     * @param                    $workflowName
1966
     * @param WorkflowDescriptor $descriptor
1967
     * @param                    $replace
1968
     *
1969
     * @return bool
1970
     *
1971
     * @throws InternalWorkflowException
1972
     */
1973
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1974
    {
1975
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1976
1977
        return $success;
1978
    }
1979
1980
1981
    /**
1982
     * Query the workflow store for matching instances
1983
     *
1984
     * @param WorkflowExpressionQuery $query
1985
     *
1986
     * @return array
1987
     *
1988
     * @throws InternalWorkflowException
1989
     */
1990
    public function query(WorkflowExpressionQuery $query)
1991
    {
1992
        return $this->getPersistence()->query($query);
1993
    }
1994
1995
    /**
1996
     * @return string
1997
     */
1998 17
    public function getDefaultTypeResolverClass()
1999
    {
2000 17
        return $this->defaultTypeResolverClass;
2001
    }
2002
2003
    /**
2004
     * @param string $defaultTypeResolverClass
2005
     *
2006
     * @return $this
2007
     */
2008
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
2009
    {
2010
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
2011
2012
        return $this;
2013
    }
2014
2015
2016
    /**
2017
     * @param ConditionsDescriptor $descriptor
2018
     * @param TransientVarsInterface $transientVars
2019
     * @param PropertySetInterface $ps
2020
     * @param                      $currentStepId
2021
     *
2022
     * @return bool
2023
     *
2024
     * @throws InternalWorkflowException
2025
     * @throws WorkflowException
2026
     */
2027 19
    protected function passesConditionsByDescriptor(ConditionsDescriptor $descriptor = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2028
    {
2029 19
        if (null === $descriptor) {
2030 17
            return true;
2031
        }
2032
2033 7
        $type = $descriptor->getType();
2034 7
        $conditions = $descriptor->getConditions();
2035 7
        if (!$conditions instanceof SplObjectStorage) {
2036
            $errMsg = 'Invalid conditions';
2037
            throw new InternalWorkflowException($errMsg);
2038
        }
2039 7
        $passesConditions = $this->passesConditionsWithType($type, $conditions, $transientVars, $ps, $currentStepId);
2040
2041 7
        return $passesConditions;
2042
    }
2043
2044
    /**
2045
     * @param string $conditionType
2046
     * @param SplObjectStorage $conditions
2047
     * @param TransientVarsInterface $transientVars
2048
     * @param PropertySetInterface $ps
2049
     * @param integer $currentStepId
2050
     *
2051
     * @return bool
2052
     *
2053
     * @throws InternalWorkflowException
2054
     * @throws InternalWorkflowException
2055
     * @throws WorkflowException
2056
     *
2057
     */
2058 13
    protected function passesConditionsWithType($conditionType, SplObjectStorage $conditions = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2059
    {
2060 13
        if (null === $conditions) {
2061
            return true;
2062
        }
2063
2064 13
        if (0 === $conditions->count()) {
2065 1
            return true;
2066
        }
2067
2068 12
        $and = strtoupper($conditionType) === 'AND';
2069 12
        $or = !$and;
2070
2071 12
        foreach ($conditions as $descriptor) {
2072 12
            if ($descriptor instanceof ConditionsDescriptor) {
2073 6
                $descriptorConditions = $descriptor->getConditions();
2074 6
                if (!$descriptorConditions instanceof SplObjectStorage) {
2075
                    $errMsg = 'Invalid conditions container';
2076
                    throw new InternalWorkflowException($errMsg);
2077
                }
2078
2079 6
                $result = $this->passesConditionsWithType($descriptor->getType(), $descriptorConditions, $transientVars, $ps, $currentStepId);
2080 12
            } elseif ($descriptor instanceof ConditionDescriptor) {
2081 12
                $result = $this->passesCondition($descriptor, $transientVars, $ps, $currentStepId);
2082 11
            } else {
2083
                $errMsg = 'Invalid condition descriptor';
2084
                throw new Exception\InternalWorkflowException($errMsg);
2085
            }
2086
2087 11
            if ($and && !$result) {
2088 1
                return false;
2089 11
            } elseif ($or && $result) {
2090 5
                return true;
2091
            }
2092 10
        }
2093
2094 9
        if ($and) {
2095 3
            return true;
2096
        }
2097
2098 6
        return false;
2099
    }
2100
2101
    /**
2102
     * @param ConditionDescriptor $conditionDesc
2103
     * @param TransientVarsInterface $transientVars
2104
     * @param PropertySetInterface $ps
2105
     * @param integer $currentStepId
2106
     *
2107
     * @return boolean
2108
     *
2109
     * @throws WorkflowException
2110
     * @throws InternalWorkflowException
2111
     */
2112 12
    protected function passesCondition(ConditionDescriptor $conditionDesc, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2113
    {
2114 12
        $type = $conditionDesc->getType();
2115
2116 12
        $argsOriginal = $conditionDesc->getArgs();
2117
2118
2119 12
        $args = $this->prepareArgs($argsOriginal, $transientVars, $ps);
2120
2121 12
        if (-1 !== $currentStepId) {
2122 7
            $stepId = array_key_exists('stepId', $args) ? (integer)$args['stepId'] : null;
2123
2124 7
            if (null !== $stepId && -1 === $stepId) {
2125 1
                $args['stepId'] = $currentStepId;
2126 1
            }
2127 7
        }
2128
2129 12
        $condition = $this->getResolver()->getCondition($type, $args);
2130
2131 11
        if (null === $condition) {
2132
            $this->context->setRollbackOnly();
2133
            $errMsg = 'Огибка при загрузки условия';
2134
            throw new WorkflowException($errMsg);
2135
        }
2136
2137
        try {
2138 11
            $passed = $condition->passesCondition($transientVars, $args, $ps);
2139
2140 11
            if ($conditionDesc->isNegate()) {
2141
                $passed = !$passed;
2142
            }
2143 11
        } catch (\Exception $e) {
2144
            $this->context->setRollbackOnly();
2145
2146
            $errMsg = sprintf(
2147
                'Ошбика при выполнение условия %s',
2148
                get_class($condition)
2149
            );
2150
2151
            throw new WorkflowException($errMsg, $e->getCode(), $e);
2152
        }
2153
2154 11
        return $passed;
2155
    }
2156
2157
    /**
2158
     * Подготавливает аргументы.
2159
     *
2160
     * @param array                  $argsOriginal
2161
     *
2162
     * @param TransientVarsInterface $transientVars
2163
     * @param PropertySetInterface   $ps
2164
     *
2165
     * @return array
2166
     *
2167
     * @throws InternalWorkflowException
2168
     */
2169 17
    protected function prepareArgs(array $argsOriginal = [], TransientVarsInterface $transientVars, PropertySetInterface $ps)
2170
    {
2171 17
        $args = [];
2172 17
        foreach ($argsOriginal as $key => $value) {
2173 17
            $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($value, $transientVars, $ps);
2174 17
            $args[$key] = $translateValue;
2175 17
        }
2176
2177 17
        return $args;
2178
    }
2179
}
2180