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 ( 909684...e1b3d2 )
by Андрей
15:20
created

AbstractWorkflow   F

Complexity

Total Complexity 272

Size/Duplication

Total Lines 2111
Duplicated Lines 1.85 %

Coupling/Cohesion

Components 1
Dependencies 38

Importance

Changes 26
Bugs 0 Features 0
Metric Value
wmc 272
c 26
b 0
f 0
lcom 1
cbo 38
dl 39
loc 2111
rs 0.5217

53 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A initLoger() 0 8 2
B initialize() 0 49 5
B populateTransientMap() 0 55 5
A validateIterateData() 0 7 3
A convertDataInArray() 0 16 4
F transitionWorkflow() 0 222 46
A buildJoinsSteps() 0 14 4
C getAvailableAutoActions() 14 58 9
C getAvailableAutoActionsForStep() 7 27 7
A completeEntry() 0 14 4
F createNewCurrentStep() 0 109 18
A transientVarsFactory() 0 5 1
D doAction() 0 78 14
B checkImplicitFinish() 0 27 5
C canModifyEntryState() 12 58 14
A getCurrentSteps() 0 4 1
A getHistorySteps() 0 4 1
A getStepFromStorage() 0 20 4
B changeEntryState() 0 37 6
B executeFunction() 0 25 4
C verifyInputs() 0 39 7
A getCurrentStep() 0 19 4
B isActionAvailable() 0 33 6
A getWorkflowDescriptorForAction() 0 17 4
B canInitialize() 0 43 6
B canInitializeInternal() 0 26 3
A getResolver() 0 13 2
A getPersistence() 0 4 1
A getConfiguration() 0 16 4
A getLog() 0 4 1
A setLog() 0 14 2
A getWorkflowDescriptor() 0 10 2
B executeTriggerFunction() 0 24 2
C getAvailableActions() 6 65 11
C getAvailableActionsForStep() 0 42 8
A setConfiguration() 0 6 1
A getEntryState() 0 16 2
A getPersistenceProperties() 0 4 1
A getPropertySet() 0 16 2
A getWorkflowNames() 0 11 2
A setTypeResolver() 0 6 1
C getSecurityPermissions() 0 59 9
A getWorkflowName() 0 19 3
A removeWorkflowDescriptor() 0 4 1
A saveWorkflowDescriptor() 0 6 1
A query() 0 4 1
A getDefaultTypeResolverClass() 0 4 1
A setDefaultTypeResolverClass() 0 6 1
A passesConditionsByDescriptor() 0 16 3
C passesConditionsWithType() 0 42 12
C passesCondition() 0 44 8
A prepareArgs() 0 10 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractWorkflow often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractWorkflow, and based on these observations, apply Extract Interface, too.

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
                $joinSteps = $this->buildJoinsSteps($currentSteps, $step, $wf, $join, $joinSteps);
0 ignored issues
show
Documentation introduced by
$currentSteps is of type object<SplObjectStorage>, but the function expects a array<integer,object<Old...low\Spi\StepInterface>>.

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...
458
459
                $historySteps = $store->findHistorySteps($entry->getId());
460
461
                $joinSteps = $this->buildJoinsSteps($historySteps, $step, $wf, $join, $joinSteps);
0 ignored issues
show
Bug introduced by
It seems like $historySteps defined by $store->findHistorySteps($entry->getId()) on line 459 can also be of type object<SplObjectStorage>; however, OldTown\Workflow\Abstrac...flow::buildJoinsSteps() does only seem to accept array<integer,object<Old...low\Spi\StepInterface>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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