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.

AbstractWorkflow   F
last analyzed

Complexity

Total Complexity 266

Size/Duplication

Total Lines 2031
Duplicated Lines 9.01 %

Coupling/Cohesion

Components 1
Dependencies 37

Test Coverage

Coverage 35.44%

Importance

Changes 21
Bugs 0 Features 0
Metric Value
wmc 266
c 21
b 0
f 0
lcom 1
cbo 37
dl 183
loc 2031
ccs 168
cts 474
cp 0.3544
rs 0.5217

47 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B initialize() 0 44 4
C populateTransientMap() 15 66 10
F transitionWorkflow() 18 227 50
C getAvailableAutoActions() 21 66 10
C getAvailableAutoActionsForStep() 7 27 7
B completeEntry() 4 17 6
F createNewCurrentStep() 0 107 17
A transientVarsFactory() 0 5 1
C doAction() 18 72 13
B checkImplicitFinish() 0 27 5
C canModifyEntryState() 12 58 14
A getCurrentSteps() 17 17 2
B changeEntryState() 0 38 6
B executeFunction() 4 29 5
C verifyInputs() 18 55 11
A getCurrentStep() 0 19 4
B isActionAvailable() 0 33 6
A getWorkflowDescriptorForAction() 0 17 4
B canInitialize() 0 37 5
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() 13 73 12
C getAvailableActionsForStep() 0 42 8
A setConfiguration() 0 6 1
A getEntryState() 0 16 2
A getHistorySteps() 16 16 2
A getPersistenceProperties() 0 4 1
A getPropertySet() 16 16 2
A getWorkflowNames() 0 11 2
A setTypeResolver() 0 6 1
B getSecurityPermissions() 0 55 8
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 12 2
D passesConditionsWithType() 0 33 10
C passesCondition() 4 48 9

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\RegisterDescriptor;
27
use OldTown\Workflow\Loader\ResultDescriptor;
28
use OldTown\Workflow\Loader\ValidatorDescriptor;
29
use OldTown\Workflow\Loader\WorkflowDescriptor;
30
use OldTown\Workflow\Query\WorkflowExpressionQuery;
31
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
32
use OldTown\Workflow\Spi\StepInterface;
33
use OldTown\Workflow\Spi\WorkflowEntryInterface;
34
use OldTown\Workflow\Spi\WorkflowStoreInterface;
35
use OldTown\Workflow\TransientVars\TransientVarsInterface;
36
use Psr\Log\LoggerInterface;
37
use Traversable;
38
use SplObjectStorage;
39
use DateTime;
40
use OldTown\Workflow\TransientVars\BaseTransientVars;
41
use ReflectionClass;
42
use ArrayObject;
43
44
45
/**
46
 * Class AbstractWorkflow
47
 *
48
 * @package OldTown\Workflow
49
 */
50
abstract class  AbstractWorkflow implements WorkflowInterface
51
{
52
    /**
53
     * @var WorkflowContextInterface
54
     */
55
    protected $context;
56
57
    /**
58
     * @var ConfigurationInterface
59
     */
60
    protected $configuration;
61
62
    /**
63
     *
64
     * @var array
65
     */
66
    protected $stateCache = [];
67
68
    /**
69
     * @var TypeResolverInterface
70
     */
71
    protected $typeResolver;
72
73
    /**
74
     * Логер
75
     *
76
     * @var LoggerInterface
77
     */
78
    protected $log;
79
80
    /**
81
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
82
     *
83
     * @var string
84
     */
85
    protected $defaultTypeResolverClass = TypeResolver::class;
86
87
    /**
88
     *
89
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
90
     * @throws \OldTown\Log\Exception\InvalidArgumentException
91
     * @throws \OldTown\Log\Exception\DomainException
92
     *
93
     */
94
    public function __construct()
95
    {
96
        $this->log = LogFactory::getLog();
97
    }
98
99
    /**
100
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
101
     * Workflow может быть инициализированно только один раз
102
     *
103
     * @param string $workflowName Имя workflow
104
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
105
     * @param TransientVarsInterface $inputs Данные введеные пользователем
106 19
     * @return integer
107
     * @throws \OldTown\Workflow\Exception\InvalidRoleException
108 19
     * @throws \OldTown\Workflow\Exception\InvalidInputException
109 19
     * @throws \OldTown\Workflow\Exception\WorkflowException
110 19
     * @throws \OldTown\Workflow\Exception\InvalidEntryStateException
111
     * @throws \OldTown\Workflow\Exception\InvalidActionException
112
     * @throws \OldTown\Workflow\Exception\FactoryException
113
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
114
     * @throws \OldTown\Workflow\Exception\StoreException
115
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
116
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
117 19
     *
118
     */
119 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
120 19
    {
121
        try {
122 19
            $initialAction = (integer)$initialAction;
123
124 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
125
126
            $store = $this->getPersistence();
127
128
            $entry = $store->createEntry($workflowName);
129
130
            $ps = $store->getPropertySet($entry->getId());
131
132
133
            if (null === $inputs) {
134
                $inputs = $this->transientVarsFactory();
135
            }
136
            $transientVars = $inputs;
137
            $inputs = clone $transientVars;
138
139
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
140
141
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
142
                $this->context->setRollbackOnly();
143
                $errMsg = 'You are restricted from initializing this workflow';
144 19
                throw new InvalidRoleException($errMsg);
145
            }
146 19
147 19
            $action = $wf->getInitialAction($initialAction);
148 19
149 19
            $currentSteps = new SplObjectStorage();
150 19
            $this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
0 ignored issues
show
Bug introduced by
It seems like $action defined by $wf->getInitialAction($initialAction) on line 147 can be null; however, OldTown\Workflow\Abstrac...w::transitionWorkflow() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
151 19
152 19
            $entryId = $entry->getId();
153 19
        } catch (WorkflowException $e) {
154
            $this->context->setRollbackOnly();
155 19
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
156 19
        }
157 19
158 19
159 19
        // now clone the memory PS to the real PS
160 19
        //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...
161 19
        return $entryId;
162 19
    }
163 19
164 19
    /**
165 19
     * @param WorkflowEntryInterface $entry
166
     * @param TransientVarsInterface $transientVars
167
     * @param array|Traversable|RegisterDescriptor[]|SplObjectStorage $registersStorage
168
     * @param integer $actionId
169
     * @param array|Traversable $currentSteps
170
     * @param PropertySetInterface $ps
171
     *
172 19
     *
173
     * @return TransientVarsInterface
174 1
     *
175 19
     * @throws \OldTown\Workflow\Exception\WorkflowException
176 19
     * @throws \OldTown\Workflow\Exception\FactoryException
177
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
178
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
179 19
     * @throws \OldTown\Workflow\Exception\StoreException
180
     */
181
    protected function populateTransientMap(WorkflowEntryInterface $entry, TransientVarsInterface $transientVars, $registersStorage, $actionId = null, $currentSteps, PropertySetInterface $ps)
182
    {
183 View Code Duplication
        if (!is_array($currentSteps) && !$currentSteps  instanceof Traversable) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
            $errMsg = 'CurrentSteps должен быть массивом, либо реализовывать интерфейс Traversable';
185
            throw new InvalidArgumentException($errMsg);
186
        }
187
188 View Code Duplication
        if ($registersStorage instanceof Traversable) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
396 2
397 2
            $store->moveToHistory($step);
398
399 2
            /** @var StepInterface[] $joinSteps */
400 2
            $joinSteps = [];
401
            $joinSteps[] = $step;
402
403 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...
404
                if ($currentStep->getId() !== $step->getId()) {
405 6
                    $stepDesc = $wf->getStep($currentStep->getStepId());
406 6
407
                    if ($stepDesc->resultsInJoin($join)) {
408
                        $joinSteps[] = $currentSteps;
409 6
                    }
410
                }
411
            }
412
413
            $historySteps = $store->findHistorySteps($entry->getId());
414 5
415 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...
416
                if ($historyStep->getId() !== $step->getId()) {
417
                    $stepDesc = $wf->getStep($historyStep->getStepId());
418
419
                    if ($stepDesc->resultsInJoin($join)) {
420
                        $joinSteps[] = $currentSteps;
421
                    }
422
                }
423
            }
424 8
425
            $jn = new JoinNodes($joinSteps);
426 8
            $transientVars['jn'] = $jn;
427 8
428 8
429
            if ($this->passesConditionsWithType(null, $joinDesc->getConditions(), $transientVars, $ps, 0)) {
430
                $joinResult = $joinDesc->getResult();
431
432 8
                $joinResultValidators = $joinResult->getValidators();
433
                if ($joinResultValidators->count() > 0) {
434 8
                    $this->verifyInputs($entry, $joinResultValidators, $transientVars, $ps);
435
                }
436
437
                foreach ($joinResult->getPreFunctions() as $function) {
438
                    $this->executeFunction($function, $transientVars, $ps);
439
                }
440
441
                $previousIds = [];
442
                $i = 1;
443
444
                foreach ($joinSteps as  $currentStep) {
445
                    if (!$historySteps->contains($currentStep) && $currentStep->getId() !== $step->getId()) {
0 ignored issues
show
Bug introduced by
The method getId does only exist in OldTown\Workflow\Spi\StepInterface, but not in SplObjectStorage.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
446
                        $store->moveToHistory($step);
447
                    }
448
449
                    $previousIds[$i] = $currentStep->getId();
450
                }
451 8
452
                if (!$action->isFinish()) {
453 8
                    $previousIds[0] = $step->getId();
454 8
                    $theResult = $joinDesc->getResult();
455 8
456 8
                    $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), null, $previousIds, $transientVars, $ps);
457
                }
458
459
                foreach ($joinResult->getPostFunctions() as $function) {
460 8
                    $this->executeFunction($function, $transientVars, $ps);
461
                }
462 8
            }
463 8
        } else {
464
            $previousIds = [];
465
466
            if (null !== $step) {
467
                $previousIds[] = $step->getId();
468 8
            }
469 6
470 6
            if (!$action->isFinish()) {
471 8
                $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), $step, $previousIds, $transientVars, $ps);
472 8
            }
473
        }
474 8
475
        if ($extraPostFunctions && $extraPostFunctions->count() > 0) {
476
            foreach ($extraPostFunctions as $function) {
477
                $this->executeFunction($function, $transientVars, $ps);
478
            }
479
        }
480
481
        if (WorkflowEntryInterface::COMPLETED !== $entry->getState() && null !== $wf->getInitialAction($action->getId())) {
482
            $this->changeEntryState($entry->getId(), WorkflowEntryInterface::ACTIVATED);
483
        }
484
485
        if ($action->isFinish()) {
486
            $this->completeEntry($action, $entry->getId(), $this->getCurrentSteps($entry->getId()), WorkflowEntryInterface::COMPLETED);
487 8
            return true;
488 8
        }
489
490
        $availableAutoActions = $this->getAvailableAutoActions($entry->getId(), $inputs);
491
492
        if (count($availableAutoActions) > 0) {
493
            $this->doAction($entry->getId(), $availableAutoActions[0], $inputs);
494
        }
495
496
        return false;
497
    }
498
499
500
    /**
501
     * @param       $id
502
     * @param TransientVarsInterface $inputs
503
     *
504
     * @return array
505
     * @throws \OldTown\Workflow\Exception\WorkflowException
506
     */
507
    protected function getAvailableAutoActions($id, TransientVarsInterface $inputs)
508
    {
509
        try {
510
            $store = $this->getPersistence();
511
            $entry = $store->findEntry($id);
512
513
            if (null === $entry) {
514
                $errMsg = sprintf(
515
                    'Нет сущности workflow c id %s',
516
                    $id
517
                );
518
                throw new InvalidArgumentException($errMsg);
519
            }
520
521
522
            if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
523
                $logMsg = sprintf('--> состояние %s', $entry->getState());
524
                $this->getLog()->debug($logMsg);
525
                return [0];
526
            }
527
528
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
529 16
530 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...
531 16
                $errMsg = sprintf(
532 16
                    'Нет workflow c именем %s',
533
                    $entry->getWorkflowName()
534 16
                );
535
                throw new InvalidArgumentException($errMsg);
536 16
            }
537
538
            $l = [];
539
            $ps = $store->getPropertySet($id);
540
            $transientVars = $inputs;
541
            $currentSteps = $store->findCurrentSteps($id);
542
543
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
544
545
            $globalActions = $wf->getGlobalActions();
546
547 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...
548
                $transientVars['actionId'] = $action->getId();
549
550
                if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
551
                    $l[] = $action->getId();
552
                }
553
            }
554
555 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...
556
                $availableAutoActionsForStep = $this->getAvailableAutoActionsForStep($wf, $step, $transientVars, $ps);
557
                foreach ($availableAutoActionsForStep as $v) {
558
                    $l[] = $v;
559
                }
560
                //$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...
561
            }
562
563
            $l = array_unique($l);
564
565
            return $l;
566
        } catch (\Exception $e) {
567
            $errMsg = 'Ошибка при проверке доступных действий';
568
            $this->getLog()->error($errMsg, [$e]);
569
        }
570
571
        return [];
572
    }
573
574
575
    /**
576
     * @param WorkflowDescriptor   $wf
577
     * @param StepInterface        $step
578
     * @param TransientVarsInterface                $transientVars
579 19
     * @param PropertySetInterface $ps
580
     *
581
     * @return array
582
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
583
     * @throws \OldTown\Workflow\Exception\WorkflowException
584
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
585
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
586
     * @throws \OldTown\Workflow\Exception\InvalidActionException
587
     */
588 19
    protected function getAvailableAutoActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
589
    {
590
        $l = [];
591
        $s = $wf->getStep($step->getStepId());
592
593
        if (null === $s) {
594
            $msg = sprintf('getAvailableAutoActionsForStep вызвана с несуществующим id %s', $step->getStepId());
595
            $this->getLog()->debug($msg);
596
            return $l;
597
        }
598
599
600
        $actions = $s->getActions();
601
        if (null === $actions || 0 === $actions->count()) {
602
            return $l;
603
        }
604
605 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...
606
            $transientVars['actionId'] = $action->getId();
607
608
            if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
609
                $l[] = $action->getId();
610
            }
611
        }
612
613 16
        return $l;
614
    }
615 16
616 16
    /**
617
     * @param ActionDescriptor $action
618 16
     * @param                  $id
619
     * @param array|Traversable $currentSteps
620
     * @param                  $state
621
     *
622 16
     * @return void
623 16
     *
624
     * @throws \OldTown\Workflow\Exception\StoreException
625
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
626
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
627
     */
628
    protected function completeEntry(ActionDescriptor $action = null, $id, $currentSteps, $state)
629
    {
630 View Code Duplication
        if (!($currentSteps instanceof Traversable || is_array($currentSteps))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
631
            $errMsg = 'CurrentSteps должен быть массивом, либо реализовывать интерфейс Traversable';
632 16
            throw new InvalidArgumentException($errMsg);
633 16
        }
634
635
636
        $this->getPersistence()->setEntryState($id, $state);
637
638
        $oldStatus = null !== $action ? $action->getUnconditionalResult()->getOldStatus() : 'Finished';
639
        $actionIdValue = null !== $action ? $action->getId() : -1;
640
        foreach ($currentSteps as $step) {
641
            $this->getPersistence()->markFinished($step, $actionIdValue, new DateTime(), $oldStatus, $this->context->getCaller());
642
            $this->getPersistence()->moveToHistory($step);
643
        }
644 16
    }
645 16
    /**
646 16
     * @param ResultDescriptor       $theResult
647 16
     * @param WorkflowEntryInterface $entry
648 16
     * @param WorkflowStoreInterface $store
649 16
     * @param integer                $actionId
650 16
     * @param StepInterface          $currentStep
651
     * @param array                  $previousIds
652
     * @param TransientVarsInterface                  $transientVars
653
     * @param PropertySetInterface   $ps
654
     *
655
     * @return StepInterface
656
     * @throws \OldTown\Workflow\Exception\WorkflowException
657
     * @throws \OldTown\Workflow\Exception\StoreException
658
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
659
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
660
     */
661
    protected function createNewCurrentStep(
662
        ResultDescriptor $theResult,
663
        WorkflowEntryInterface $entry,
664
        WorkflowStoreInterface $store,
665
        $actionId,
666
        StepInterface $currentStep = null,
667
        array $previousIds = [],
668
        TransientVarsInterface $transientVars,
669
        PropertySetInterface $ps
670
    ) {
671
        try {
672
            $nextStep = $theResult->getStep();
673
674
            if (-1 === $nextStep) {
675
                if (null !== $currentStep) {
676
                    $nextStep = $currentStep->getStepId();
677
                } else {
678
                    $errMsg = 'Неверный аргумент. Новый шаг является таким же как текущий. Но текущий шаг не указан';
679
                    throw new StoreException($errMsg);
680
                }
681
            }
682
683
            $owner = $theResult->getOwner();
684
685
            $logMsg = sprintf(
686
                'Результат: stepId=%s, status=%s, owner=%s, actionId=%s, currentStep=%s',
687
                $nextStep,
688
                $theResult->getStatus(),
689
                $owner,
690
                $actionId,
691
                null !== $currentStep ? $currentStep->getId() : 0
692
            );
693
            $this->getLog()->debug($logMsg);
694
695
            $variableResolver = $this->getConfiguration()->getVariableResolver();
696
697
            if (null !== $owner) {
698
                $o = $variableResolver->translateVariables($owner, $transientVars, $ps);
699
                $owner = null !== $o ? (string)$o : null;
700
            }
701
702
703
            $oldStatus = $theResult->getOldStatus();
704
            $oldStatus = (string)$variableResolver->translateVariables($oldStatus, $transientVars, $ps);
705
706
            $status = $theResult->getStatus();
707
            $status = (string)$variableResolver->translateVariables($status, $transientVars, $ps);
708
709
710
            if (null !== $currentStep) {
711
                $store->markFinished($currentStep, $actionId, new DateTime(), $oldStatus, $this->context->getCaller());
712
                $store->moveToHistory($currentStep);
713
            }
714
715
            $startDate = new DateTime();
716
            $dueDate = null;
717
718
            $theResultDueDate = (string)$theResult->getDueDate();
719
            $theResultDueDate = trim($theResultDueDate);
720
            if (strlen($theResultDueDate) > 0) {
721
                $dueDateObject = $variableResolver->translateVariables($theResultDueDate, $transientVars, $ps);
722
723
                if ($dueDateObject instanceof DateTime) {
724
                    $dueDate = $dueDateObject;
725
                } elseif (is_string($dueDateObject)) {
726
                    $dueDate = new DateTime($dueDate);
727
                } elseif (is_numeric($dueDateObject)) {
728
                    $dueDate = DateTime::createFromFormat('U', $dueDateObject);
729
                } else {
730
                    $errMsg = 'Ошибка при преобразование DueData';
731 19
                    throw new InternalWorkflowException($errMsg);
732
                }
733 19
            }
734
735 19
            $newStep = $store->createCurrentStep($entry->getId(), $nextStep, $owner, $startDate, $dueDate, $status, $previousIds);
0 ignored issues
show
Security Bug introduced by
It seems like $dueDate defined by \DateTime::createFromFormat('U', $dueDateObject) on line 728 can also be of type false; however, OldTown\Workflow\Spi\Wor...ce::createCurrentStep() does only seem to accept null|object<DateTime>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
736
            $transientVars['createdStep'] =  $newStep;
737 19
738
            if (null === $currentStep && 0 === count($previousIds)) {
739
                $currentSteps = [];
740
                $currentSteps[] = $newStep;
741
                $transientVars['currentSteps'] =  $currentSteps;
742
            }
743
744
            if (! $transientVars->offsetExists('descriptor')) {
745 19
                $errMsg = 'Ошибка при получение дескриптора workflow из transientVars';
746
                throw new InternalWorkflowException($errMsg);
747
            }
748 19
749 19
            /** @var WorkflowDescriptor $descriptor */
750 2
            $descriptor = $transientVars['descriptor'];
751 2
            $step = $descriptor->getStep($nextStep);
752
753 19
            if (null === $step) {
754 19
                $errMsg = sprintf('Шаг #%s не найден', $nextStep);
755
                throw new WorkflowException($errMsg);
756 19
            }
757
758
            $preFunctions = $step->getPreFunctions();
759
760
            foreach ($preFunctions as $function) {
761
                $this->executeFunction($function, $transientVars, $ps);
762
            }
763
        } catch (WorkflowException $e) {
764 19
            $this->context->setRollbackOnly();
765
            throw $e;
766 19
        }
767 16
    }
768
769
    /**
770 19
     * Создает хранилище переменных
771 19
     *
772 19
     * @param $class
773 19
     *
774
     * @return TransientVarsInterface
775 19
     */
776
    protected function transientVarsFactory($class = BaseTransientVars::class)
777
    {
778
        $r = new \ReflectionClass($class);
779
        return $r->newInstance();
780
    }
781
782
    /**
783
     *
784
     *
785 19
     * Perform an action on the specified workflow instance.
786
     * @param integer $id The workflow instance id.
787 19
     * @param integer $actionId The action id to perform (action id's are listed in the workflow descriptor).
788
     * @param TransientVarsInterface $inputs The inputs to the workflow instance.
789
     * @throws \OldTown\Workflow\Exception\InvalidInputException if a validator is specified and an input is invalid.
790
     * @throws WorkflowException if the action is invalid for the specified workflow
791
     * instance's current state.
792
     *
793
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
794
     * @throws \OldTown\Workflow\Exception\StoreException
795
     * @throws \OldTown\Workflow\Exception\FactoryException
796
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
797
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
798
     * @throws \OldTown\Workflow\Exception\InvalidActionException
799
     * @throws \OldTown\Workflow\Exception\InvalidEntryStateException
800 19
     * @throws \OldTown\Workflow\Exception\WorkflowException
801
     */
802 19
    public function doAction($id, $actionId, TransientVarsInterface $inputs = null)
803
    {
804 19
        $actionId = (integer)$actionId;
805
        if (null === $inputs) {
806
            $inputs = $this->transientVarsFactory();
807
        }
808
        $transientVars = $inputs;
809
        $inputs = clone $transientVars;
810
811
        $store = $this->getPersistence();
812
        $entry = $store->findEntry($id);
813
814 19
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
815
            return;
816
        }
817
818
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
819
820 17
        $currentSteps = $store->findCurrentSteps($id);
821
        $action = null;
822 17
823
        $ps = $store->getPropertySet($id);
824
825
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
826
827
828
        $validAction = false;
829
830 View Code Duplication
        foreach ($wf->getGlobalActions() as $actionDesc) {
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...
831
            if ($actionId === $actionDesc->getId()) {
832
                $action = $actionDesc;
833
834
                if ($this->isActionAvailable($action, $transientVars, $ps, 0)) {
835
                    $validAction = true;
836
                }
837
            }
838
        }
839
840
841
        foreach ($currentSteps as $step) {
842
            $s = $wf->getStep($step->getStepId());
843
844 View Code Duplication
            foreach ($s->getActions() as $actionDesc) {
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...
845
                if ($actionId === $actionDesc->getId()) {
846
                    $action = $actionDesc;
847
848
                    if ($this->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
849
                        $validAction = true;
850
                    }
851
                }
852
            }
853
        }
854
855
856
        if (!$validAction) {
857
            $errMsg = sprintf(
858
                'Action %s is invalid',
859
                $actionId
860
            );
861
            throw new InvalidActionException($errMsg);
862
        }
863
864
865
        try {
866
            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...
867
                $this->checkImplicitFinish($action, $id);
868
            }
869
        } catch (WorkflowException $e) {
870
            $this->context->setRollbackOnly();
871
            throw $e;
872
        }
873
    }
874
875
    /**
876
     * @param ActionDescriptor $action
877
     * @param                  $id
878
     *
879
     * @return void
880
     *
881
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
882
     * @throws \OldTown\Workflow\Exception\StoreException
883
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
884
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
885
     *
886
     */
887
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
888
    {
889
        $store = $this->getPersistence();
890
        $entry = $store->findEntry($id);
891
892
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
893
894
        $currentSteps = $store->findCurrentSteps($id);
895
896
        $isCompleted = $wf->getGlobalActions()->count() === 0;
897
898
        foreach ($currentSteps as $step) {
899
            if ($isCompleted) {
900
                break;
901
            }
902
903
            $stepDes = $wf->getStep($step->getStepId());
904
905
            if ($stepDes->getActions()->count() > 0) {
906
                $isCompleted = true;
907
            }
908
        }
909
910
        if ($isCompleted) {
911
            $this->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
912
        }
913
    }
914
915
    /**
916
     *
917
     * Check if the state of the specified workflow instance can be changed to the new specified one.
918
     * @param integer $id The workflow instance id.
919
     * @param integer $newState The new state id.
920
     * @return boolean true if the state of the workflow can be modified, false otherwise.
921
     * @throws \OldTown\Workflow\Exception\StoreException
922
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
923
     *
924
     */
925
    public function canModifyEntryState($id, $newState)
926
    {
927
        $store = $this->getPersistence();
928
        $entry = $store->findEntry($id);
929
930
        $currentState = $entry->getState();
931
932
        $result = false;
933
        try {
934
            switch ($newState) {
935
                case WorkflowEntryInterface::COMPLETED: {
936
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
937
                        $result = true;
938
                    }
939
                    break;
940
                }
941
942
                //@TODO Разобраться с бизнес логикой. Может быть нужно добавить break
943
                /** @noinspection PhpMissingBreakStatementInspection */
944
                case WorkflowEntryInterface::CREATED: {
945
                    $result = false;
946
                }
947 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...
948
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
949
                        $result = true;
950
                    }
951
                    break;
952
                }
953
                case WorkflowEntryInterface::SUSPENDED: {
954
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
955
                        $result = true;
956
                    }
957
                    break;
958
                }
959 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...
960
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::ACTIVATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
961
                        $result = true;
962
                    }
963
                    break;
964
                }
965
                default: {
966
                    $result = false;
967
                    break;
968 19
                }
969
970 19
            }
971
972 19
            return $result;
973
        } 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...
974
            $errMsg = sprintf(
975
                'Ошибка проверки изменения состояния для инстанса #%s',
976
                $id
977
            );
978
            $this->getLog()->error($errMsg, [$e]);
979
        }
980
981
        return false;
982
    }
983
984
985
    /**
986
     *
987
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
988
     *
989
     * @param integer $id id экземпляра workflow
990
     * @return SplObjectStorage|StepInterface[]
991
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
992
     */
993 View Code Duplication
    public function getCurrentSteps($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
994
    {
995
        try {
996
            $store = $this->getPersistence();
997
998
            return $store->findCurrentSteps($id);
999
        } catch (StoreException $e) {
1000
            $errMsg = sprintf(
1001
                'Ошибка при проверке текущего шага для инстанса # %s',
1002
                $id
1003
            );
1004
            $this->getLog()->error($errMsg, [$e]);
1005
1006
1007
            return [];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array(); (array) is incompatible with the return type declared by the interface OldTown\Workflow\Workflo...erface::getCurrentSteps of type SplObjectStorage|OldTown...low\Spi\StepInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1008
        }
1009
    }
1010
1011
    /**
1012
     *
1013
     *
1014
     * Modify the state of the specified workflow instance.
1015
     * @param integer $id The workflow instance id.
1016
     * @param integer $newState the new state to change the workflow instance to.
1017
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1018
     * @throws \OldTown\Workflow\Exception\StoreException
1019
     * @throws \OldTown\Workflow\Exception\InvalidEntryStateException
1020
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1021
     */
1022
    public function changeEntryState($id, $newState)
1023
    {
1024
        $store = $this->getPersistence();
1025
        $entry = $store->findEntry($id);
1026
1027
1028
        if ($newState === $entry->getState()) {
1029
            return;
1030
        }
1031
1032
        if ($this->canModifyEntryState($id, $newState)) {
1033
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
1034
                $currentSteps = $this->getCurrentSteps($id);
1035
1036
                if (count($currentSteps) > 0) {
1037
                    $this->completeEntry(null, $id, $currentSteps, $newState);
1038
                }
1039
            }
1040
1041
            $store->setEntryState($id, $newState);
1042
        } else {
1043
            $errMsg = sprintf(
1044
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
1045
                $id,
1046
                $entry->getState(),
1047
                $newState
1048
            );
1049
1050
            throw new InvalidEntryStateException($errMsg);
1051
        }
1052
1053
        $msg = sprintf(
1054
            '%s : Новое состояние: %s',
1055
            $entry->getId(),
1056
            $entry->getState()
1057
        );
1058
        $this->getLog()->debug($msg);
1059
    }
1060
1061
1062
    /**
1063
     * @param FunctionDescriptor $function
1064
     * @param TransientVarsInterface $transientVars
1065
     * @param PropertySetInterface $ps
1066
     *
1067
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1068
     * @throws \OldTown\Workflow\Exception\WorkflowException
1069
     */
1070
    protected function executeFunction(FunctionDescriptor $function, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1071
    {
1072
        if (null !== $function) {
1073
            $type = $function->getType();
1074
1075
            $argsOriginal = $function->getArgs();
1076
            $args = [];
1077
1078 View Code Duplication
            foreach ($argsOriginal as $k => $v) {
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...
1079
                $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($v, $transientVars, $ps);
1080
                $args[$k] = $translateValue;
1081
            }
1082
1083
            $provider = $this->getResolver()->getFunction($type, $args);
1084
1085
            if (null === $provider) {
1086
                $this->context->setRollbackOnly();
1087
                $errMsg = 'Не загружен провайдер для функции';
1088
                throw new WorkflowException($errMsg);
1089
            }
1090
1091
            try {
1092
                $provider->execute($transientVars, $args, $ps);
1093
            } catch (WorkflowException $e) {
1094
                $this->context->setRollbackOnly();
1095
                throw $e;
1096
            }
1097
        }
1098
    }
1099
1100
1101
    /**
1102
     * @param WorkflowEntryInterface $entry
1103
     * @param $validatorsStorage
1104
     * @param TransientVarsInterface $transientVars
1105
     * @param PropertySetInterface $ps
1106
     * @throws \OldTown\Workflow\Exception\WorkflowException
1107
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1108
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1109
     * @throws \OldTown\Workflow\Exception\InvalidInputException
1110
     */
1111
    protected function verifyInputs(WorkflowEntryInterface $entry, $validatorsStorage, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1112
    {
1113 View Code Duplication
        if ($validatorsStorage instanceof Traversable) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1114
            $validators = [];
1115
            foreach ($validatorsStorage as $k => $v) {
1116
                $validators[$k] = $v;
1117
            }
1118
        } elseif (is_array($validatorsStorage)) {
1119
            $validators = $validatorsStorage;
1120
        } else {
1121
            $errMsg = sprintf(
1122
                'Validators должен быть массивом, либо реализовывать интерфейс Traversable. EntryId: %s',
1123
                $entry->getId()
1124
            );
1125
            throw new InvalidArgumentException($errMsg);
1126
        }
1127
1128
        /** @var ValidatorDescriptor[] $validators */
1129
        foreach ($validators as $input) {
1130
            if (null !== $input) {
1131
                $type = $input->getType();
1132
                $argsOriginal = $input->getArgs();
1133
1134
                $args = [];
1135
1136 View Code Duplication
                foreach ($argsOriginal as $k => $v) {
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...
1137
                    $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($v, $transientVars, $ps);
1138
                    $args[$k] = $translateValue;
1139
                }
1140
1141
1142
                $validator = $this->getResolver()->getValidator($type, $args);
1143
1144
                if (null === $validator) {
1145
                    $this->context->setRollbackOnly();
1146
                    $errMsg = 'Ошибка при загрузке валидатора';
1147
                    throw new WorkflowException($errMsg);
1148
                }
1149
1150
                try {
1151
                    $validator->validate($transientVars, $args, $ps);
1152
                } catch (InvalidInputException $e) {
1153
                    throw $e;
1154
                } catch (\Exception $e) {
1155
                    $this->context->setRollbackOnly();
1156
1157
                    if ($e instanceof WorkflowException) {
1158
                        throw $e;
1159
                    }
1160
1161
                    throw new WorkflowException($e->getMessage(), $e->getCode(), $e);
1162
                }
1163
            }
1164
        }
1165
    }
1166
1167
1168
    /**
1169
     * Возвращает текущий шаг
1170
     *
1171
     * @param WorkflowDescriptor $wfDesc
1172
     * @param integer $actionId
1173
     * @param StepInterface[]|SplObjectStorage $currentSteps
1174
     * @param TransientVarsInterface $transientVars
1175
     * @param PropertySetInterface $ps
1176
     *
1177
     * @return StepInterface
1178
     * @throws \OldTown\Workflow\Exception\WorkflowException
1179
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1180
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1181
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1182
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1183
     */
1184
    protected function getCurrentStep(WorkflowDescriptor $wfDesc, $actionId, SplObjectStorage $currentSteps, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1185
    {
1186
        if (1 === $currentSteps->count()) {
1187
            $currentSteps->rewind();
1188
            return $currentSteps->current();
1189
        }
1190
1191
1192
        foreach ($currentSteps as $step) {
1193
            $stepId = $step->getId();
1194
            $action = $wfDesc->getStep($stepId)->getAction($actionId);
1195
1196
            if ($this->isActionAvailable($action, $transientVars, $ps, $stepId)) {
1197
                return $step;
1198
            }
1199
        }
1200
1201
        return null;
1202
    }
1203
1204
    /**
1205
     * @param ActionDescriptor|null $action
1206
     * @param TransientVarsInterface $transientVars
1207
     * @param PropertySetInterface $ps
1208
     * @param $stepId
1209
     *
1210
     * @return boolean
1211
     *
1212
     * @throws \OldTown\Workflow\Exception\WorkflowException
1213
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1214
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1215
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1216 19
     */
1217
    protected function isActionAvailable(ActionDescriptor $action = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $stepId)
1218 19
    {
1219
        if (null === $action) {
1220
            return false;
1221
        }
1222
1223
        $result = null;
1224
        $actionHash = spl_object_hash($action);
1225
1226
        $result = array_key_exists($actionHash, $this->stateCache) ? $this->stateCache[$actionHash] : $result;
1227
1228
        $wf = $this->getWorkflowDescriptorForAction($action);
1229
1230
1231
        if (null === $result) {
1232
            $restriction = $action->getRestriction();
1233
            $conditions = null;
1234
1235
            if (null !== $restriction) {
1236
                $conditions = $restriction->getConditionsDescriptor();
1237 19
            }
1238
1239 19
            $result = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $stepId)
1240
                && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $stepId);
1241
1242
            $this->stateCache[$actionHash] = $result;
1243
        }
1244
1245
1246
        $result = (boolean)$result;
1247
1248
        return $result;
1249
    }
1250
1251
    /**
1252
     * По дейсвтию получаем дексрипторв workflow
1253
     *
1254
     * @param ActionDescriptor $action
1255
     * @return WorkflowDescriptor
1256
     *
1257
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1258
     */
1259
    private function getWorkflowDescriptorForAction(ActionDescriptor $action)
1260
    {
1261
        $objWfd = $action;
1262
1263
        $count = 0;
1264
        while (!$objWfd instanceof WorkflowDescriptor || null === $objWfd) {
1265
            $objWfd = $objWfd->getParent();
1266
1267
            $count++;
1268
            if ($count > 10) {
1269
                $errMsg = 'Ошибка при получение WorkflowDescriptor';
1270
                throw new InternalWorkflowException($errMsg);
1271
            }
1272
        }
1273
1274
        return $objWfd;
1275
    }
1276
1277
1278
    /**
1279
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1280
     *
1281
     * @param string $workflowName имя workflow
1282
     * @param integer $initialAction id начального состояния
1283
     * @param TransientVarsInterface $inputs
1284
     *
1285
     * @return bool
1286
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1287
     * @throws \OldTown\Workflow\Exception\FactoryException
1288
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1289
     * @throws \OldTown\Workflow\Exception\StoreException
1290
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1291
     */
1292
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
1293
    {
1294
        $mockWorkflowName = $workflowName;
1295
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
1296
1297
        try {
1298
            $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...
1299
        } catch (\Exception $e) {
1300
            $errMsg = sprintf('Ошибка при создание PropertySer: %s', $e->getMessage());
1301
            throw new InternalWorkflowException($errMsg);
1302
        }
1303
1304
        if (null === $inputs) {
1305
            $inputs = $this->transientVarsFactory();
1306
        }
1307
        $transientVars = $inputs;
1308
1309
        try {
1310
            $this->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
0 ignored issues
show
Bug introduced by
It seems like $ps defined by \OldTown\PropertySet\Pro...nstance('memory', null) on line 1298 can be null; however, OldTown\Workflow\Abstrac...:populateTransientMap() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1311
1312
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
0 ignored issues
show
Bug introduced by
It seems like $ps defined by \OldTown\PropertySet\Pro...nstance('memory', null) on line 1298 can be null; however, OldTown\Workflow\Abstrac...canInitializeInternal() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1313
1314
            return $result;
1315
        } catch (InvalidActionException $e) {
1316
            $this->getLog()->error($e->getMessage(), [$e]);
1317
1318
            return false;
1319
        } catch (WorkflowException $e) {
1320
            $errMsg = sprintf(
1321
                'Ошибка при проверки canInitialize: %s',
1322
                $e->getMessage()
1323
            );
1324
            $this->getLog()->error($errMsg, [$e]);
1325
1326
            return false;
1327
        }
1328
    }
1329
1330
1331
    /**
1332
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1333
     *
1334
     * @param string $workflowName имя workflow
1335
     * @param integer $initialAction id начального состояния
1336
     * @param TransientVarsInterface $transientVars
1337
     *
1338
     * @param PropertySetInterface $ps
1339
     *
1340
     * @return bool
1341
     *
1342
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1343
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1344
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1345
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1346
     * @throws \OldTown\Workflow\Exception\WorkflowException
1347
     */
1348
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1349
    {
1350
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
1351
1352
        $actionDescriptor = $wf->getInitialAction($initialAction);
1353
1354
        if (null === $actionDescriptor) {
1355
            $errMsg = sprintf(
1356
                'Некорректное инициирующие действие # %s',
1357
                $initialAction
1358
            );
1359
            throw new InvalidActionException($errMsg);
1360
        }
1361
1362
        $restriction = $actionDescriptor->getRestriction();
1363
1364
1365
        $conditions = null;
1366
        if (null !== $restriction) {
1367
            $conditions = $restriction->getConditionsDescriptor();
1368
        }
1369
1370
        $passesConditions = $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1371
1372
        return $passesConditions;
1373
    }
1374
1375
    /**
1376
     * Возвращает резолвер
1377
     *
1378
     * @return TypeResolverInterface
1379
     */
1380
    public function getResolver()
1381
    {
1382
        if (null !== $this->typeResolver) {
1383
            return $this->typeResolver;
1384
        }
1385
1386
        $classResolver = $this->getDefaultTypeResolverClass();
1387
        $r = new ReflectionClass($classResolver);
1388
        $resolver = $r->newInstance();
1389
        $this->typeResolver = $resolver;
1390
1391
        return $this->typeResolver;
1392
    }
1393
1394
    /**
1395
     * Возвращает хранилище состояния workflow
1396
     *
1397
     * @return WorkflowStoreInterface
1398
     * @throws \OldTown\Workflow\Exception\StoreException
1399
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1400
     */
1401
    protected function getPersistence()
1402
    {
1403
        return $this->getConfiguration()->getWorkflowStore();
1404
    }
1405
1406
    /**
1407
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
1408
     * инициализирует ее.
1409
     *
1410
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
1411
     *
1412
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
1413
     *
1414
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1415
     */
1416
    public function getConfiguration()
1417
    {
1418
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
1419
1420
        if (!$config->isInitialized()) {
1421
            try {
1422
                $config->load(null);
1423
            } catch (FactoryException $e) {
1424
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
1425
                $this->getLog()->critical($errMsg, ['exception' => $e]);
1426
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1427
            }
1428
        }
1429
1430
        return $config;
1431
    }
1432
1433
    /**
1434
     * @return LoggerInterface
1435
     */
1436
    public function getLog()
1437
    {
1438
        return $this->log;
1439
    }
1440
1441
    /**
1442
     * @param LoggerInterface $log
1443
     *
1444
     * @return $this
1445
     * @throws InternalWorkflowException
1446
     */
1447
    public function setLog($log)
1448
    {
1449
        try {
1450
            LogFactory::validLogger($log);
1451
        } catch (\Exception $e) {
1452
            $errMsg = 'Ошибка при валидации логера';
1453
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1454
        }
1455
1456
1457
        $this->log = $log;
1458
1459
        return $this;
1460
    }
1461
1462
1463
    /**
1464
     * Get the workflow descriptor for the specified workflow name.
1465
     *
1466
     * @param string $workflowName The workflow name.
1467
     * @return WorkflowDescriptor
1468
     * @throws InternalWorkflowException
1469
     */
1470
    public function getWorkflowDescriptor($workflowName)
1471
    {
1472
        try {
1473
            return $this->getConfiguration()->getWorkflow($workflowName);
1474
        } catch (FactoryException $e) {
1475
            $errMsg = 'Ошибка при загрузке workflow';
1476
            $this->getLog()->error($errMsg, ['exception' => $e]);
1477
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1478
        }
1479
    }
1480
1481
1482
    /**
1483
     * Executes a special trigger-function using the context of the given workflow instance id.
1484
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
1485
     * @param integer $id The workflow instance id
1486
     * @param integer $triggerId The id of the special trigger-function
1487
     * @throws \OldTown\Workflow\Exception\WorkflowException
1488
     * @throws \OldTown\Workflow\Exception\StoreException
1489
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1490
     * @throws \OldTown\Workflow\Exception\FactoryException
1491
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1492
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1493
     */
1494
    public function executeTriggerFunction($id, $triggerId)
1495
    {
1496
        $store = $this->getPersistence();
1497
        $entry = $store->findEntry($id);
1498
1499
        if (null === $entry) {
1500
            $errMsg = sprintf(
1501
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
1502
                $triggerId,
1503
                $id
1504
            );
1505
            $this->getLog()->warning($errMsg);
1506
            return;
1507
        }
1508
1509
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1510
1511
        $ps = $store->getPropertySet($id);
1512
        $transientVars = $this->transientVarsFactory();
1513
1514
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
1515
1516
        $this->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
1517
    }
1518
1519
    /**
1520
     * @param $id
1521
     * @param $inputs
1522
     *
1523
     * @return array
1524
     * @throws \OldTown\Workflow\Exception\StoreException
1525
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1526
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1527
     * @throws \OldTown\Workflow\Exception\WorkflowException
1528
     * @throws \OldTown\Workflow\Exception\FactoryException
1529
     */
1530
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
1531
    {
1532
        try {
1533
            $store = $this->getPersistence();
1534
            $entry = $store->findEntry($id);
1535
1536
            if (null === $entry) {
1537
                $errMsg = sprintf(
1538
                    'Не существует экземпляра workflow c id %s',
1539
                    $id
1540
                );
1541
                throw new InvalidArgumentException($errMsg);
1542
            }
1543
1544
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
1545
                return [];
1546
            }
1547
1548
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1549
1550 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...
1551
                $errMsg = sprintf(
1552
                    'Не существует workflow c именем %s',
1553
                    $entry->getWorkflowName()
1554
                );
1555
                throw new InvalidArgumentException($errMsg);
1556
            }
1557
1558
            $l = [];
1559
            $ps = $store->getPropertySet($id);
1560
1561
            $transientVars = $inputs;
1562
            if (null === $transientVars) {
1563
                $transientVars = $this->transientVarsFactory();
1564
            }
1565
1566
            $currentSteps = $store->findCurrentSteps($id);
1567
1568
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
1569
1570
            $globalActions = $wf->getGlobalActions();
1571
1572
            foreach ($globalActions as $action) {
1573
                $restriction = $action->getRestriction();
1574
                $conditions = null;
1575
1576
                $transientVars['actionId'] = $action->getId();
1577
1578
                if (null !== $restriction) {
1579
                    $conditions = $restriction->getConditionsDescriptor();
1580
                }
1581
1582
                $flag = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1583
                if ($flag) {
1584
                    $l[] = $action->getId();
1585
                }
1586
            }
1587
1588
1589 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...
1590
                $data = $this->getAvailableActionsForStep($wf, $step, $transientVars, $ps);
1591
                foreach ($data as $v) {
1592
                    $l[] = $v;
1593
                }
1594
            }
1595
            return array_unique($l);
1596
        } catch (\Exception $e) {
1597
            $errMsg = 'Ошибка проверки доступных действий';
1598
            $this->getLog()->error($errMsg, [$e]);
1599
        }
1600
1601
        return [];
1602
    }
1603
1604
    /**
1605
     * @param WorkflowDescriptor   $wf
1606
     * @param StepInterface        $step
1607
     * @param TransientVarsInterface                $transientVars
1608
     * @param PropertySetInterface $ps
1609
     *
1610
     * @return array
1611
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1612
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1613
     * @throws \OldTown\Workflow\Exception\WorkflowException
1614
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1615
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1616
     */
1617
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1618
    {
1619
        $l = [];
1620
        $s = $wf->getStep($step->getStepId());
1621
1622
        if (null === $s) {
1623
            $errMsg = sprintf(
1624
                'getAvailableActionsForStep вызван с не существующим id шага %s',
1625
                $step->getStepId()
1626
            );
1627
1628
            $this->getLog()->warning($errMsg);
1629
1630
            return $l;
1631
        }
1632
1633
        $actions  = $s->getActions();
1634
1635
        if (null === $actions || 0  === $actions->count()) {
1636
            return $l;
1637
        }
1638
1639
        foreach ($actions as $action) {
1640
            $restriction = $action->getRestriction();
1641
            $conditions = null;
1642
1643
            $transientVars['actionId'] = $action->getId();
1644
1645
1646
            if (null !== $restriction) {
1647
                $conditions = $restriction->getConditionsDescriptor();
1648
            }
1649
1650
            $f = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
1651
                 && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
1652
            if ($f) {
1653
                $l[] = $action->getId();
1654
            }
1655
        }
1656
1657
        return $l;
1658
    }
1659
1660
    /**
1661
     * @param ConfigurationInterface $configuration
1662
     *
1663
     * @return $this
1664
     */
1665
    public function setConfiguration(ConfigurationInterface $configuration)
1666
    {
1667
        $this->configuration = $configuration;
1668
1669
        return $this;
1670
    }
1671
1672
    /**
1673
     * Возвращает состояние для текущего экземпляра workflow
1674
     *
1675
     * @param integer $id id экземпляра workflow
1676
     * @return integer id текущего состояния
1677
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1678
     */
1679
    public function getEntryState($id)
1680
    {
1681
        try {
1682
            $store = $this->getPersistence();
1683
1684
            return $store->findEntry($id)->getState();
1685
        } catch (StoreException $e) {
1686
            $errMsg = sprintf(
1687
                'Ошибка при получение состояния экземпляра workflow c id# %s',
1688
                $id
1689
            );
1690
            $this->getLog()->error($errMsg, [$e]);
1691
        }
1692
1693
        return WorkflowEntryInterface::UNKNOWN;
1694
    }
1695
1696
    /**
1697
     * Returns a list of all steps that are completed for the given workflow instance id.
1698
     *
1699
     * @param integer $id The workflow instance id.
1700
     * @return StepInterface[] a List of Steps
1701
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1702
     */
1703 View Code Duplication
    public function getHistorySteps($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
1704
    {
1705
        try {
1706
            $store = $this->getPersistence();
1707
1708
            return $store->findHistorySteps($id);
0 ignored issues
show
Bug Compatibility introduced by
The expression $store->findHistorySteps($id); of type OldTown\Workflow\Spi\Ste...face[]|SplObjectStorage adds the type SplObjectStorage to the return on line 1708 which is incompatible with the return type declared by the interface OldTown\Workflow\Workflo...erface::getHistorySteps of type OldTown\Workflow\Spi\StepInterface[].
Loading history...
1709
        } catch (StoreException $e) {
1710
            $errMsg = sprintf(
1711
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
1712
                $id
1713
            );
1714
            $this->getLog()->error($errMsg, [$e]);
1715
        }
1716
1717
        return [];
1718
    }
1719
1720
    /**
1721
     * Настройки хранилища
1722
     *
1723
     * @return array
1724
     *
1725
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1726
     */
1727
    public function getPersistenceProperties()
1728
    {
1729
        return $this->getConfiguration()->getPersistenceArgs();
1730
    }
1731
1732
1733
    /**
1734
     * Get the PropertySet for the specified workflow instance id.
1735
     * @param integer $id The workflow instance id.
1736
     * @return PropertySetInterface
1737
     *
1738
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1739
     */
1740 View Code Duplication
    public function getPropertySet($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
1741
    {
1742
        $ps = null;
1743
1744
        try {
1745
            $ps = $this->getPersistence()->getPropertySet($id);
1746
        } catch (StoreException $e) {
1747
            $errMsg = sprintf(
1748
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1749
                $id
1750
            );
1751
            $this->getLog()->error($errMsg, [$e]);
1752
        }
1753
1754
        return $ps;
1755
    }
1756
1757
    /**
1758
     * @return \String[]
1759
     *
1760
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1761
     */
1762
    public function getWorkflowNames()
1763
    {
1764
        try {
1765
            return $this->getConfiguration()->getWorkflowNames();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getConfigu...()->getWorkflowNames(); (string[]) is incompatible with the return type documented by OldTown\Workflow\Abstrac...kflow::getWorkflowNames of type String[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1766
        } catch (FactoryException $e) {
1767
            $errMsg = 'Ошибка при получение имен workflow';
1768
            $this->getLog()->error($errMsg, [$e]);
1769
        }
1770
1771
        return [];
1772
    }
1773
1774
    /**
1775
     * @param TypeResolverInterface $typeResolver
1776
     *
1777
     * @return $this
1778
     */
1779
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1780
    {
1781
        $this->typeResolver = $typeResolver;
1782
1783
        return $this;
1784
    }
1785
1786
1787
    /**
1788
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1789
     * @param integer $id id the workflow instance id.
1790
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1791
     * @return array  A List of permissions specified currently (a permission is a string name).
1792
     *
1793
     */
1794
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1795
    {
1796
        try {
1797
            $store = $this->getPersistence();
1798
            $entry = $store->findEntry($id);
1799
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1800
1801
            $ps = $store->getPropertySet($id);
1802
1803
            if (null === $inputs) {
1804
                $inputs = $this->transientVarsFactory();
1805
            }
1806
            $transientVars = $inputs;
1807
1808
            $currentSteps = $store->findCurrentSteps($id);
1809
1810
            try {
1811
                $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1812
            } catch (\Exception $e) {
1813
                $errMsg = sprintf(
1814
                    'Внутреннея ошибка: %s',
1815
                    $e->getMessage()
1816
                );
1817
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1818
            }
1819
1820
1821
            $s = [];
1822
1823
            foreach ($currentSteps as $step) {
1824
                $stepId = $step->getStepId();
1825
1826
                $xmlStep = $wf->getStep($stepId);
1827
1828
                $securities = $xmlStep->getPermissions();
1829
1830
                foreach ($securities as $security) {
1831
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1832
                    if (null !== $security->getRestriction() && $this->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1833
                        $s[$security->getName()] = $security->getName();
1834
                    }
1835
                }
1836
            }
1837
1838
            return $s;
1839
        } catch (\Exception $e) {
1840
            $errMsg = sprintf(
1841
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1842
                $id
1843
            );
1844
            $this->getLog()->error($errMsg, [$e]);
1845
        }
1846
1847
        return [];
1848
    }
1849
1850
1851
    /**
1852
     * Get the name of the specified workflow instance.
1853
     *
1854
     * @param integer $id the workflow instance id.
1855
     * @return string
1856
     *
1857
     * @throws \OldTown\Workflow\Exception\StoreException
1858
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1859
     */
1860
    public function getWorkflowName($id)
1861
    {
1862
        try {
1863
            $store = $this->getPersistence();
1864
            $entry = $store->findEntry($id);
1865
1866
            if (null !== $entry) {
1867
                return $entry->getWorkflowName();
1868
            }
1869
        } catch (FactoryException $e) {
1870
            $errMsg = sprintf(
1871
                'Ошибка при получение имен workflow для инстанса с id # %s',
1872
                $id
1873
            );
1874
            $this->getLog()->error($errMsg, [$e]);
1875
        }
1876
1877
        return null;
1878
    }
1879
1880
    /**
1881
     * Удаляет workflow
1882
     *
1883
     * @param string $workflowName
1884
     *
1885
     * @return bool
1886
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1887
     */
1888
    public function removeWorkflowDescriptor($workflowName)
1889
    {
1890
        return $this->getConfiguration()->removeWorkflow($workflowName);
1891
    }
1892
1893
    /**
1894
     * @param                    $workflowName
1895
     * @param WorkflowDescriptor $descriptor
1896
     * @param                    $replace
1897
     *
1898
     * @return bool
1899
     *
1900
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1901
     */
1902
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1903
    {
1904
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1905
1906
        return $success;
1907
    }
1908
1909
1910
    /**
1911
     * Query the workflow store for matching instances
1912
     *
1913
     * @param WorkflowExpressionQuery $query
1914
     *
1915
     * @return array
1916
     *
1917
     * @throws \OldTown\Workflow\Exception\WorkflowException
1918
     * @throws \OldTown\Workflow\Exception\StoreException
1919
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1920
1921
     */
1922
    public function query(WorkflowExpressionQuery $query)
1923
    {
1924
        return $this->getPersistence()->query($query);
1925
    }
1926
1927
    /**
1928
     * @return string
1929
     */
1930
    public function getDefaultTypeResolverClass()
1931
    {
1932
        return $this->defaultTypeResolverClass;
1933
    }
1934
1935
    /**
1936
     * @param string $defaultTypeResolverClass
1937
     *
1938
     * @return $this
1939
     */
1940
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1941
    {
1942
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1943
1944
        return $this;
1945
    }
1946
1947
1948
    /**
1949
     * @param ConditionsDescriptor $descriptor
1950
     * @param TransientVarsInterface $transientVars
1951
     * @param PropertySetInterface $ps
1952
     * @param                      $currentStepId
1953
     *
1954
     * @return bool
1955
     *
1956
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1957
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1958
     * @throws \OldTown\Workflow\Exception\WorkflowException
1959
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1960
     */
1961
    protected function passesConditionsByDescriptor(ConditionsDescriptor $descriptor = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
1962
    {
1963
        if (null === $descriptor) {
1964
            return true;
1965
        }
1966
1967
        $type = $descriptor->getType();
1968
        $conditions = $descriptor->getConditions();
1969
        $passesConditions = $this->passesConditionsWithType($type, $conditions, $transientVars, $ps, $currentStepId);
0 ignored issues
show
Bug introduced by
It seems like $conditions defined by $descriptor->getConditions() on line 1968 can also be of type array<integer,object<Old...\ConditionsDescriptor>>; however, OldTown\Workflow\Abstrac...sesConditionsWithType() does only seem to accept null|object<SplObjectStorage>, 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...
1970
1971
        return $passesConditions;
1972
    }
1973
1974
    /**
1975
     * @param string $conditionType
1976
     * @param SplObjectStorage $conditions
1977
     * @param TransientVarsInterface $transientVars
1978
     * @param PropertySetInterface $ps
1979
     * @param integer $currentStepId
1980
     *
1981
     * @return bool
1982
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1983
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1984
     * @throws \OldTown\Workflow\Exception\WorkflowException
1985
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1986
     */
1987
    protected function passesConditionsWithType($conditionType, SplObjectStorage $conditions = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
1988
    {
1989
        if (null === $conditions) {
1990
            return true;
1991
        }
1992
1993
        if (0 === $conditions->count()) {
1994
            return true;
1995
        }
1996
1997
        $and = strtoupper($conditionType) === 'AND';
1998
        $or = !$and;
1999
2000
        foreach ($conditions as $descriptor) {
2001
            if ($descriptor instanceof ConditionsDescriptor) {
2002
                $result = $this->passesConditionsWithType($descriptor->getType(), $descriptor->getConditions(), $transientVars, $ps, $currentStepId);
0 ignored issues
show
Bug introduced by
It seems like $descriptor->getConditions() targeting OldTown\Workflow\Loader\...riptor::getConditions() can also be of type array<integer,object<Old...\ConditionsDescriptor>>; however, OldTown\Workflow\Abstrac...sesConditionsWithType() does only seem to accept null|object<SplObjectStorage>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2003
            } else {
2004
                $result = $this->passesCondition($descriptor, $transientVars, $ps, $currentStepId);
2005
            }
2006
2007
            if ($and && !$result) {
2008
                return false;
2009
            } elseif ($or && $result) {
2010
                return true;
2011
            }
2012
        }
2013
2014
        if ($and) {
2015
            return true;
2016
        }
2017
2018
        return false;
2019
    }
2020
2021
    /**
2022
     * @param ConditionDescriptor $conditionDesc
2023
     * @param TransientVarsInterface $transientVars
2024
     * @param PropertySetInterface $ps
2025
     * @param integer $currentStepId
2026
     *
2027
     * @return boolean
2028
     *
2029
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
2030
     * @throws \OldTown\Workflow\Exception\WorkflowException
2031
     */
2032
    protected function passesCondition(ConditionDescriptor $conditionDesc, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2033
    {
2034
        $type = $conditionDesc->getType();
2035
2036
        $argsOriginal = $conditionDesc->getArgs();
2037
2038
2039
        $args = [];
2040 View Code Duplication
        foreach ($argsOriginal as $key => $value) {
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...
2041
            $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($value, $transientVars, $ps);
2042
            $args[$key] = $translateValue;
2043
        }
2044
2045
        if (-1 !== $currentStepId) {
2046
            $stepId = array_key_exists('stepId', $args) ? (integer)$args['stepId'] : null;
2047
2048
            if (null !== $stepId && -1 === $stepId) {
2049
                $args['stepId'] = $currentStepId;
2050
            }
2051
        }
2052
2053
        $condition = $this->getResolver()->getCondition($type, $args);
2054
2055
        if (null === $condition) {
2056
            $this->context->setRollbackOnly();
2057
            $errMsg = 'Огибка при загрузки условия';
2058
            throw new WorkflowException($errMsg);
2059
        }
2060
2061
        try {
2062
            $passed = $condition->passesCondition($transientVars, $args, $ps);
2063
2064
            if ($conditionDesc->isNegate()) {
2065
                $passed = !$passed;
2066
            }
2067
        } catch (\Exception $e) {
2068
            $this->context->setRollbackOnly();
2069
2070
            $errMsg = sprintf(
2071
                'Ошбика при выполнение условия %s',
2072
                get_class($condition)
2073
            );
2074
2075
            throw new WorkflowException($errMsg, $e->getCode(), $e);
2076
        }
2077
2078
        return $passed;
2079
    }
2080
}
2081