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

AbstractWorkflow::populateTransientMap()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 55
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
c 8
b 0
f 0
dl 0
loc 55
rs 8.7752
cc 5
eloc 33
nc 10
nop 6

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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