GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 138d30...5011e7 )
by Андрей
07:49
created

AbstractWorkflow   F

Complexity

Total Complexity 266

Size/Duplication

Total Lines 2034
Duplicated Lines 9.83 %

Coupling/Cohesion

Components 1
Dependencies 37

Importance

Changes 34
Bugs 0 Features 0
Metric Value
wmc 266
c 34
b 0
f 0
lcom 1
cbo 37
dl 200
loc 2034
rs 0.5217

47 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B initialize() 0 44 4
B executeTriggerFunction() 0 24 2
C populateTransientMap() 15 66 10
F transitionWorkflow() 35 227 50
C getAvailableAutoActions() 21 66 10
C getAvailableAutoActionsForStep() 7 27 7
B completeEntry() 4 17 6
F createNewCurrentStep() 0 111 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 37 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
C getAvailableActions() 13 73 12
C getAvailableActionsForStep() 0 42 8
A setConfiguration() 0 6 1
A getEntryState() 0 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
A getHistorySteps() 16 16 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
/**
3
 * @link https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow;
7
8
use OldTown\Log\LogFactory;
9
use OldTown\PropertySet\PropertySetInterface;
10
use OldTown\PropertySet\PropertySetManager;
11
use OldTown\Workflow\Config\ConfigurationInterface;
12
use OldTown\Workflow\Config\DefaultConfiguration;
13
use OldTown\Workflow\Exception\FactoryException;
14
use OldTown\Workflow\Exception\InternalWorkflowException;
15
use OldTown\Workflow\Exception\InvalidActionException;
16
use OldTown\Workflow\Exception\InvalidArgumentException;
17
use OldTown\Workflow\Exception\InvalidEntryStateException;
18
use OldTown\Workflow\Exception\InvalidInputException;
19
use OldTown\Workflow\Exception\InvalidRoleException;
20
use OldTown\Workflow\Exception\StoreException;
21
use OldTown\Workflow\Exception\WorkflowException;
22
use OldTown\Workflow\Loader\ActionDescriptor;
23
use OldTown\Workflow\Loader\ConditionDescriptor;
24
use OldTown\Workflow\Loader\ConditionsDescriptor;
25
use OldTown\Workflow\Loader\FunctionDescriptor;
26
use OldTown\Workflow\Loader\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
     * @return integer
107
     * @throws \OldTown\Workflow\Exception\InvalidRoleException
108
     * @throws \OldTown\Workflow\Exception\InvalidInputException
109
     * @throws \OldTown\Workflow\Exception\WorkflowException
110
     * @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
     *
118
     */
119
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
120
    {
121
        try {
122
            $initialAction = (integer)$initialAction;
123
124
            $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
                throw new InvalidRoleException($errMsg);
145
            }
146
147
            $action = $wf->getInitialAction($initialAction);
148
149
            $currentSteps = new SplObjectStorage();
150
            $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
152
            $entryId = $entry->getId();
153
        } catch (WorkflowException $e) {
154
            $this->context->setRollbackOnly();
155
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
156
        }
157
158
159
        // now clone the memory PS to the real PS
160
        //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
        return $entryId;
162
    }
163
164
    /**
165
     * @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
     *
173
     * @return TransientVarsInterface
174
     *
175
     * @throws \OldTown\Workflow\Exception\WorkflowException
176
     * @throws \OldTown\Workflow\Exception\FactoryException
177
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
178
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
179
     * @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
        }
199
        /** @var RegisterDescriptor[] $registers */
200
201
        $transientVars['context'] = $this->context;
202
        $transientVars['entry'] = $entry;
203
        $transientVars['entryId'] = $entry->getId();
204
        $transientVars['store'] = $this->getPersistence();
205
        $transientVars['configuration'] = $this->getConfiguration();
206
        $transientVars['descriptor'] = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
207
208
        if (null !== $actionId) {
209
            $transientVars['actionId'] = $actionId;
210
        }
211
212
        $transientVars['currentSteps'] = $currentSteps;
213
214
215
        foreach ($registers as $register) {
216
            $args = $register->getArgs();
217
            $type = $register->getType();
218
219
            try {
220
                $r = $this->getResolver()->getRegister($type, $args);
221
            } catch (\Exception $e) {
222
                $errMsg = 'Ошибка при инициализации register';
223
                $this->context->setRollbackOnly();
224
                throw new WorkflowException($errMsg, $e->getCode(), $e);
225
            }
226
227
            $variableName = $register->getVariableName();
228
            try {
229
                $value = $r->registerVariable($this->context, $entry, $args, $ps);
230
231
                $transientVars[$variableName] = $value;
232
            } catch (\Exception $e) {
233
                $this->context->setRollbackOnly();
234
235
                $errMsg = sprintf(
236
                    'При получение значения переменной %s из registry %s произошла ошибка',
237
                    $variableName,
238
                    get_class($r)
239
                );
240
241
                throw new WorkflowException($errMsg, $e->getCode(), $e);
242
            }
243
        }
244
245
        return $transientVars;
246
    }
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
     * @param TransientVarsInterface $transientVars
257
     * @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
356
            foreach ($splitPreFunctions as $function) {
357
                $this->executeFunction($function, $transientVars, $ps);
358
            }
359
360
            if (!$action->isFinish()) {
361
                $moveFirst = true;
362
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
//                $theResults = [];
365
//                foreach ($results as $result) {
366
//                    $theResults[] = $result;
367
//                }
368
369
                foreach ($results as $resultDescriptor) {
370
                    $moveToHistoryStep = null;
371
372
                    if ($moveFirst) {
373
                        $moveToHistoryStep = $step;
374
                    }
375
376
                    $previousIds = [];
377
378
                    if (null !== $step) {
379
                        $previousIds[] = $step->getStepId();
380
                    }
381
382
                    $this->createNewCurrentStep($resultDescriptor, $entry, $store, $action->getId(), $moveToHistoryStep, $previousIds, $transientVars, $ps);
383
                    $moveFirst = false;
384
                }
385
            }
386
387
388
            foreach ($splitPostFunctions as $function) {
389
                $this->executeFunction($function, $transientVars, $ps);
390
            }
391
        } elseif (null !== $join && 0 !== $join) {
392
            $joinDesc = $wf->getJoin($join);
393
            $oldStatus = $theResult->getOldStatus();
394
            $caller = $this->context->getCaller();
395
            $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
397
            $store->moveToHistory($step);
398
399
            /** @var StepInterface[] $joinSteps */
400
            $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
                    $stepDesc = $wf->getStep($currentStep->getStepId());
406
407
                    if ($stepDesc->resultsInJoin($join)) {
408
                        $joinSteps[] = $currentSteps;
409
                    }
410
                }
411
            }
412
413
            $historySteps = $store->findHistorySteps($entry->getId());
414
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
425
            $jn = new JoinNodes($joinSteps);
426
            $transientVars['jn'] = $jn;
427
428
429
            if ($this->passesConditionsWithType(null, $joinDesc->getConditions(), $transientVars, $ps, 0)) {
430
                $joinResult = $joinDesc->getResult();
431
432
                $joinResultValidators = $joinResult->getValidators();
433
                if ($joinResultValidators->count() > 0) {
434
                    $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
452 View Code Duplication
                if (!$action->isFinish()) {
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...
453
                    $previousIds[0] = $step->getId();
454
                    $theResult = $joinDesc->getResult();
455
456
                    $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), null, $previousIds, $transientVars, $ps);
457
                }
458
459
                foreach ($joinResult->getPostFunctions() as $function) {
460
                    $this->executeFunction($function, $transientVars, $ps);
461
                }
462
            }
463 View Code Duplication
        } else {
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...
464
            $previousIds = [];
465
466
            if (null !== $step) {
467
                $previousIds[] = $step->getId();
468
            }
469
470
            if (!$action->isFinish()) {
471
                $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), $step, $previousIds, $transientVars, $ps);
472
            }
473
        }
474
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
            return true;
488
        }
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
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
                $errMsg = sprintf(
532
                    'Нет workflow c именем %s',
533
                    $entry->getWorkflowName()
534
                );
535
                throw new InvalidArgumentException($errMsg);
536
            }
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
     * @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
    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
        return $l;
614
    }
615
616
    /**
617
     * @param ActionDescriptor $action
618
     * @param                  $id
619
     * @param array|Traversable $currentSteps
620
     * @param                  $state
621
     *
622
     * @return void
623
     *
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
            throw new InvalidArgumentException($errMsg);
633
        }
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
    }
645
    /**
646
     * @param ResultDescriptor       $theResult
647
     * @param WorkflowEntryInterface $entry
648
     * @param WorkflowStoreInterface $store
649
     * @param integer                $actionId
650
     * @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 = [],
0 ignored issues
show
Unused Code introduced by
The parameter $previousIds is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
668
        TransientVarsInterface $transientVars,
669
        PropertySetInterface $ps
670
    ) {
671
672
        //@FIXME Решить проблему с сохранением previus step
673
        $previousIds = [];
674
675
        try {
676
            $nextStep = $theResult->getStep();
677
678
            if (-1 === $nextStep) {
679
                if (null !== $currentStep) {
680
                    $nextStep = $currentStep->getStepId();
681
                } else {
682
                    $errMsg = 'Неверный аргумент. Новый шаг является таким же как текущий. Но текущий шаг не указан';
683
                    throw new StoreException($errMsg);
684
                }
685
            }
686
687
            $owner = $theResult->getOwner();
688
689
            $logMsg = sprintf(
690
                'Результат: stepId=%s, status=%s, owner=%s, actionId=%s, currentStep=%s',
691
                $nextStep,
692
                $theResult->getStatus(),
693
                $owner,
694
                $actionId,
695
                null !== $currentStep ? $currentStep->getId() : 0
696
            );
697
            $this->getLog()->debug($logMsg);
698
699
            $variableResolver = $this->getConfiguration()->getVariableResolver();
700
701
            if (null !== $owner) {
702
                $o = $variableResolver->translateVariables($owner, $transientVars, $ps);
703
                $owner = null !== $o ? (string)$o : null;
704
            }
705
706
707
            $oldStatus = $theResult->getOldStatus();
708
            $oldStatus = (string)$variableResolver->translateVariables($oldStatus, $transientVars, $ps);
709
710
            $status = $theResult->getStatus();
711
            $status = (string)$variableResolver->translateVariables($status, $transientVars, $ps);
712
713
714
            if (null !== $currentStep) {
715
                $store->markFinished($currentStep, $actionId, new DateTime(), $oldStatus, $this->context->getCaller());
716
                $store->moveToHistory($currentStep);
717
            }
718
719
            $startDate = new DateTime();
720
            $dueDate = null;
721
722
            $theResultDueDate = (string)$theResult->getDueDate();
723
            $theResultDueDate = trim($theResultDueDate);
724
            if (strlen($theResultDueDate) > 0) {
725
                $dueDateObject = $variableResolver->translateVariables($theResultDueDate, $transientVars, $ps);
726
727
                if ($dueDateObject instanceof DateTime) {
728
                    $dueDate = $dueDateObject;
729
                } elseif (is_string($dueDateObject)) {
730
                    $dueDate = new DateTime($dueDate);
731
                } elseif (is_numeric($dueDateObject)) {
732
                    $dueDate = DateTime::createFromFormat('U', $dueDateObject);
733
                } else {
734
                    $errMsg = 'Ошибка при преобразование DueData';
735
                    throw new InternalWorkflowException($errMsg);
736
                }
737
            }
738
739
            $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 732 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...
740
            $transientVars['createdStep'] =  $newStep;
741
742
            if (null === $currentStep && 0 === count($previousIds)) {
743
                $currentSteps = [];
744
                $currentSteps[] = $newStep;
745
                $transientVars['currentSteps'] =  $currentSteps;
746
            }
747
748
            if (! $transientVars->offsetExists('descriptor')) {
749
                $errMsg = 'Ошибка при получение дескриптора workflow из transientVars';
750
                throw new InternalWorkflowException($errMsg);
751
            }
752
753
            /** @var WorkflowDescriptor $descriptor */
754
            $descriptor = $transientVars['descriptor'];
755
            $step = $descriptor->getStep($nextStep);
756
757
            if (null === $step) {
758
                $errMsg = sprintf('Шаг #%s не найден', $nextStep);
759
                throw new WorkflowException($errMsg);
760
            }
761
762
            $preFunctions = $step->getPreFunctions();
763
764
            foreach ($preFunctions as $function) {
765
                $this->executeFunction($function, $transientVars, $ps);
766
            }
767
        } catch (WorkflowException $e) {
768
            $this->context->setRollbackOnly();
769
            throw $e;
770
        }
771
    }
772
773
    /**
774
     * Создает хранилище переменных
775
     *
776
     * @param $class
777
     *
778
     * @return TransientVarsInterface
779
     */
780
    protected function transientVarsFactory($class = BaseTransientVars::class)
781
    {
782
        $r = new \ReflectionClass($class);
783
        return $r->newInstance();
784
    }
785
786
    /**
787
     *
788
     *
789
     * Perform an action on the specified workflow instance.
790
     * @param integer $id The workflow instance id.
791
     * @param integer $actionId The action id to perform (action id's are listed in the workflow descriptor).
792
     * @param TransientVarsInterface $inputs The inputs to the workflow instance.
793
     * @throws \OldTown\Workflow\Exception\InvalidInputException if a validator is specified and an input is invalid.
794
     * @throws WorkflowException if the action is invalid for the specified workflow
795
     * instance's current state.
796
     *
797
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
798
     * @throws \OldTown\Workflow\Exception\StoreException
799
     * @throws \OldTown\Workflow\Exception\FactoryException
800
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
801
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
802
     * @throws \OldTown\Workflow\Exception\InvalidActionException
803
     * @throws \OldTown\Workflow\Exception\InvalidEntryStateException
804
     * @throws \OldTown\Workflow\Exception\WorkflowException
805
     */
806
    public function doAction($id, $actionId, TransientVarsInterface $inputs = null)
807
    {
808
        $actionId = (integer)$actionId;
809
        if (null === $inputs) {
810
            $inputs = $this->transientVarsFactory();
811
        }
812
        $transientVars = $inputs;
813
        $inputs = clone $transientVars;
814
815
        $store = $this->getPersistence();
816
        $entry = $store->findEntry($id);
817
818
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
819
            return;
820
        }
821
822
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
823
824
        $currentSteps = $store->findCurrentSteps($id);
825
        $action = null;
826
827
        $ps = $store->getPropertySet($id);
828
829
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
830
831
832
        $validAction = false;
833
834 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...
835
            if ($actionId === $actionDesc->getId()) {
836
                $action = $actionDesc;
837
838
                if ($this->isActionAvailable($action, $transientVars, $ps, 0)) {
839
                    $validAction = true;
840
                }
841
            }
842
        }
843
844
845
        foreach ($currentSteps as $step) {
846
            $s = $wf->getStep($step->getStepId());
847
848 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...
849
                if ($actionId === $actionDesc->getId()) {
850
                    $action = $actionDesc;
851
852
                    if ($this->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
853
                        $validAction = true;
854
                    }
855
                }
856
            }
857
        }
858
859
860
        if (!$validAction) {
861
            $errMsg = sprintf(
862
                'Action %s is invalid',
863
                $actionId
864
            );
865
            throw new InvalidActionException($errMsg);
866
        }
867
868
869
        try {
870
            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...
871
                $this->checkImplicitFinish($action, $id);
872
            }
873
        } catch (WorkflowException $e) {
874
            $this->context->setRollbackOnly();
875
            throw $e;
876
        }
877
    }
878
879
    /**
880
     * @param ActionDescriptor $action
881
     * @param                  $id
882
     *
883
     * @return void
884
     *
885
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
886
     * @throws \OldTown\Workflow\Exception\StoreException
887
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
888
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
889
     *
890
     */
891
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
892
    {
893
        $store = $this->getPersistence();
894
        $entry = $store->findEntry($id);
895
896
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
897
898
        $currentSteps = $store->findCurrentSteps($id);
899
900
        $isCompleted = $wf->getGlobalActions()->count() === 0;
901
902
        foreach ($currentSteps as $step) {
903
            if ($isCompleted) {
904
                break;
905
            }
906
907
            $stepDes = $wf->getStep($step->getStepId());
908
909
            if ($stepDes->getActions()->count() > 0) {
910
                $isCompleted = true;
911
            }
912
        }
913
914
        if ($isCompleted) {
915
            $this->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
916
        }
917
    }
918
919
    /**
920
     *
921
     * Check if the state of the specified workflow instance can be changed to the new specified one.
922
     * @param integer $id The workflow instance id.
923
     * @param integer $newState The new state id.
924
     * @return boolean true if the state of the workflow can be modified, false otherwise.
925
     * @throws \OldTown\Workflow\Exception\StoreException
926
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
927
     *
928
     */
929
    public function canModifyEntryState($id, $newState)
930
    {
931
        $store = $this->getPersistence();
932
        $entry = $store->findEntry($id);
933
934
        $currentState = $entry->getState();
935
936
        $result = false;
937
        try {
938
            switch ($newState) {
939
                case WorkflowEntryInterface::COMPLETED: {
940
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
941
                        $result = true;
942
                    }
943
                    break;
944
                }
945
946
                //@TODO Разобраться с бизнес логикой. Может быть нужно добавить break
947
                /** @noinspection PhpMissingBreakStatementInspection */
948
                case WorkflowEntryInterface::CREATED: {
949
                    $result = false;
950
                }
951 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...
952
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
953
                        $result = true;
954
                    }
955
                    break;
956
                }
957
                case WorkflowEntryInterface::SUSPENDED: {
958
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
959
                        $result = true;
960
                    }
961
                    break;
962
                }
963 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...
964
                    if (WorkflowEntryInterface::CREATED === $currentState || WorkflowEntryInterface::ACTIVATED === $currentState || WorkflowEntryInterface::SUSPENDED === $currentState) {
965
                        $result = true;
966
                    }
967
                    break;
968
                }
969
                default: {
970
                    $result = false;
971
                    break;
972
                }
973
974
            }
975
976
            return $result;
977
        } 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...
978
            $errMsg = sprintf(
979
                'Ошибка проверки изменения состояния для инстанса #%s',
980
                $id
981
            );
982
            $this->getLog()->error($errMsg, [$e]);
983
        }
984
985
        return false;
986
    }
987
988
989
    /**
990
     *
991
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
992
     *
993
     * @param integer $id id экземпляра workflow
994
     * @return array
995
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
996
     */
997 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...
998
    {
999
        try {
1000
            $store = $this->getPersistence();
1001
1002
            return $store->findCurrentSteps($id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $store->findCurrentSteps($id); (OldTown\Workflow\Spi\StepInterface[]) is incompatible with the return type declared by the interface OldTown\Workflow\Workflo...erface::getCurrentSteps of type SplObjectStorage.

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...
1003
        } catch (StoreException $e) {
1004
            $errMsg = sprintf(
1005
                'Ошибка при проверке текущего шага для инстанса # %s',
1006
                $id
1007
            );
1008
            $this->getLog()->error($errMsg, [$e]);
1009
1010
1011
            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.

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...
1012
        }
1013
    }
1014
1015
    /**
1016
     *
1017
     *
1018
     * Modify the state of the specified workflow instance.
1019
     * @param integer $id The workflow instance id.
1020
     * @param integer $newState the new state to change the workflow instance to.
1021
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1022
     * @throws \OldTown\Workflow\Exception\StoreException
1023
     * @throws \OldTown\Workflow\Exception\InvalidEntryStateException
1024
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1025
     */
1026
    public function changeEntryState($id, $newState)
1027
    {
1028
        $store = $this->getPersistence();
1029
        $entry = $store->findEntry($id);
1030
1031
        if ($newState === $entry->getState()) {
1032
            return;
1033
        }
1034
1035
        if ($this->canModifyEntryState($id, $newState)) {
1036
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
1037
                $currentSteps = $this->getCurrentSteps($id);
1038
1039
                if (count($currentSteps) > 0) {
1040
                    $this->completeEntry(null, $id, $currentSteps, $newState);
1041
                }
1042
            }
1043
1044
            $store->setEntryState($id, $newState);
1045
        } else {
1046
            $errMsg = sprintf(
1047
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
1048
                $id,
1049
                $entry->getState(),
1050
                $newState
1051
            );
1052
1053
            throw new InvalidEntryStateException($errMsg);
1054
        }
1055
1056
        $msg = sprintf(
1057
            '%s : Новое состояние: %s',
1058
            $entry->getId(),
1059
            $entry->getState()
1060
        );
1061
        $this->getLog()->debug($msg);
1062
    }
1063
1064
1065
    /**
1066
     * @param FunctionDescriptor $function
1067
     * @param TransientVarsInterface $transientVars
1068
     * @param PropertySetInterface $ps
1069
     *
1070
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1071
     * @throws \OldTown\Workflow\Exception\WorkflowException
1072
     */
1073
    protected function executeFunction(FunctionDescriptor $function, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1074
    {
1075
        if (null !== $function) {
1076
            $type = $function->getType();
1077
1078
            $argsOriginal = $function->getArgs();
1079
            $args = [];
1080
1081 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...
1082
                $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($v, $transientVars, $ps);
1083
                $args[$k] = $translateValue;
1084
            }
1085
1086
            $provider = $this->getResolver()->getFunction($type, $args);
1087
1088
            if (null === $provider) {
1089
                $this->context->setRollbackOnly();
1090
                $errMsg = 'Не загружен провайдер для функции';
1091
                throw new WorkflowException($errMsg);
1092
            }
1093
1094
            try {
1095
                $provider->execute($transientVars, $args, $ps);
1096
            } catch (WorkflowException $e) {
1097
                $this->context->setRollbackOnly();
1098
                throw $e;
1099
            }
1100
        }
1101
    }
1102
1103
1104
    /**
1105
     * @param WorkflowEntryInterface $entry
1106
     * @param $validatorsStorage
1107
     * @param TransientVarsInterface $transientVars
1108
     * @param PropertySetInterface $ps
1109
     * @throws \OldTown\Workflow\Exception\WorkflowException
1110
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1111
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1112
     * @throws \OldTown\Workflow\Exception\InvalidInputException
1113
     */
1114
    protected function verifyInputs(WorkflowEntryInterface $entry, $validatorsStorage, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1115
    {
1116 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...
1117
            $validators = [];
1118
            foreach ($validatorsStorage as $k => $v) {
1119
                $validators[$k] = $v;
1120
            }
1121
        } elseif (is_array($validatorsStorage)) {
1122
            $validators = $validatorsStorage;
1123
        } else {
1124
            $errMsg = sprintf(
1125
                'Validators должен быть массивом, либо реализовывать интерфейс Traversable. EntryId: %s',
1126
                $entry->getId()
1127
            );
1128
            throw new InvalidArgumentException($errMsg);
1129
        }
1130
1131
        /** @var ValidatorDescriptor[] $validators */
1132
        foreach ($validators as $input) {
1133
            if (null !== $input) {
1134
                $type = $input->getType();
1135
                $argsOriginal = $input->getArgs();
1136
1137
                $args = [];
1138
1139 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...
1140
                    $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($v, $transientVars, $ps);
1141
                    $args[$k] = $translateValue;
1142
                }
1143
1144
1145
                $validator = $this->getResolver()->getValidator($type, $args);
1146
1147
                if (null === $validator) {
1148
                    $this->context->setRollbackOnly();
1149
                    $errMsg = 'Ошибка при загрузке валидатора';
1150
                    throw new WorkflowException($errMsg);
1151
                }
1152
1153
                try {
1154
                    $validator->validate($transientVars, $args, $ps);
1155
                } catch (InvalidInputException $e) {
1156
                    throw $e;
1157
                } catch (\Exception $e) {
1158
                    $this->context->setRollbackOnly();
1159
1160
                    if ($e instanceof WorkflowException) {
1161
                        throw $e;
1162
                    }
1163
1164
                    throw new WorkflowException($e->getMessage(), $e->getCode(), $e);
1165
                }
1166
            }
1167
        }
1168
    }
1169
1170
1171
    /**
1172
     * Возвращает текущий шаг
1173
     *
1174
     * @param WorkflowDescriptor $wfDesc
1175
     * @param integer $actionId
1176
     * @param StepInterface[]|SplObjectStorage $currentSteps
1177
     * @param TransientVarsInterface $transientVars
1178
     * @param PropertySetInterface $ps
1179
     *
1180
     * @return StepInterface
1181
     * @throws \OldTown\Workflow\Exception\WorkflowException
1182
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1183
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1184
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1185
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1186
     */
1187
    protected function getCurrentStep(WorkflowDescriptor $wfDesc, $actionId, SplObjectStorage $currentSteps, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1188
    {
1189
        if (1 === $currentSteps->count()) {
1190
            $currentSteps->rewind();
1191
            return $currentSteps->current();
1192
        }
1193
1194
1195
        foreach ($currentSteps as $step) {
1196
            $stepId = $step->getId();
1197
            $action = $wfDesc->getStep($stepId)->getAction($actionId);
1198
1199
            if ($this->isActionAvailable($action, $transientVars, $ps, $stepId)) {
1200
                return $step;
1201
            }
1202
        }
1203
1204
        return null;
1205
    }
1206
1207
    /**
1208
     * @param ActionDescriptor|null $action
1209
     * @param TransientVarsInterface $transientVars
1210
     * @param PropertySetInterface $ps
1211
     * @param $stepId
1212
     *
1213
     * @return boolean
1214
     *
1215
     * @throws \OldTown\Workflow\Exception\WorkflowException
1216
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1217
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1218
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1219
     */
1220
    protected function isActionAvailable(ActionDescriptor $action = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $stepId)
1221
    {
1222
        if (null === $action) {
1223
            return false;
1224
        }
1225
1226
        $result = null;
1227
        $actionHash = spl_object_hash($action);
1228
1229
        $result = array_key_exists($actionHash, $this->stateCache) ? $this->stateCache[$actionHash] : $result;
1230
1231
        $wf = $this->getWorkflowDescriptorForAction($action);
1232
1233
1234
        if (null === $result) {
1235
            $restriction = $action->getRestriction();
1236
            $conditions = null;
1237
1238
            if (null !== $restriction) {
1239
                $conditions = $restriction->getConditionsDescriptor();
1240
            }
1241
1242
            $result = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $stepId)
1243
                && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $stepId);
1244
1245
            $this->stateCache[$actionHash] = $result;
1246
        }
1247
1248
1249
        $result = (boolean)$result;
1250
1251
        return $result;
1252
    }
1253
1254
    /**
1255
     * По дейсвтию получаем дексрипторв workflow
1256
     *
1257
     * @param ActionDescriptor $action
1258
     * @return WorkflowDescriptor
1259
     *
1260
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1261
     */
1262
    private function getWorkflowDescriptorForAction(ActionDescriptor $action)
1263
    {
1264
        $objWfd = $action;
1265
1266
        $count = 0;
1267
        while (!$objWfd instanceof WorkflowDescriptor || null === $objWfd) {
1268
            $objWfd = $objWfd->getParent();
1269
1270
            $count++;
1271
            if ($count > 10) {
1272
                $errMsg = 'Ошибка при получение WorkflowDescriptor';
1273
                throw new InternalWorkflowException($errMsg);
1274
            }
1275
        }
1276
1277
        return $objWfd;
1278
    }
1279
1280
1281
    /**
1282
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1283
     *
1284
     * @param string $workflowName имя workflow
1285
     * @param integer $initialAction id начального состояния
1286
     * @param TransientVarsInterface $inputs
1287
     *
1288
     * @return bool
1289
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1290
     * @throws \OldTown\Workflow\Exception\FactoryException
1291
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1292
     * @throws \OldTown\Workflow\Exception\StoreException
1293
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1294
     */
1295
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
1296
    {
1297
        $mockWorkflowName = $workflowName;
1298
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
1299
1300
        try {
1301
            $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...
1302
        } catch (\Exception $e) {
1303
            $errMsg = sprintf('Ошибка при создание PropertySer: %s', $e->getMessage());
1304
            throw new InternalWorkflowException($errMsg);
1305
        }
1306
1307
        if (null === $inputs) {
1308
            $inputs = $this->transientVarsFactory();
1309
        }
1310
        $transientVars = $inputs;
1311
1312
        try {
1313
            $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 1301 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...
1314
1315
            $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 1301 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...
1316
1317
            return $result;
1318
        } catch (InvalidActionException $e) {
1319
            $this->getLog()->error($e->getMessage(), [$e]);
1320
1321
            return false;
1322
        } catch (WorkflowException $e) {
1323
            $errMsg = sprintf(
1324
                'Ошибка при проверки canInitialize: %s',
1325
                $e->getMessage()
1326
            );
1327
            $this->getLog()->error($errMsg, [$e]);
1328
1329
            return false;
1330
        }
1331
    }
1332
1333
1334
    /**
1335
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
1336
     *
1337
     * @param string $workflowName имя workflow
1338
     * @param integer $initialAction id начального состояния
1339
     * @param TransientVarsInterface $transientVars
1340
     *
1341
     * @param PropertySetInterface $ps
1342
     *
1343
     * @return bool
1344
     *
1345
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1346
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1347
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1348
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1349
     * @throws \OldTown\Workflow\Exception\WorkflowException
1350
     */
1351
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1352
    {
1353
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
1354
1355
        $actionDescriptor = $wf->getInitialAction($initialAction);
1356
1357
        if (null === $actionDescriptor) {
1358
            $errMsg = sprintf(
1359
                'Некорректное инициирующие действие # %s',
1360
                $initialAction
1361
            );
1362
            throw new InvalidActionException($errMsg);
1363
        }
1364
1365
        $restriction = $actionDescriptor->getRestriction();
1366
1367
1368
        $conditions = null;
1369
        if (null !== $restriction) {
1370
            $conditions = $restriction->getConditionsDescriptor();
1371
        }
1372
1373
        $passesConditions = $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1374
1375
        return $passesConditions;
1376
    }
1377
1378
    /**
1379
     * Возвращает резолвер
1380
     *
1381
     * @return TypeResolverInterface
1382
     */
1383
    public function getResolver()
1384
    {
1385
        if (null !== $this->typeResolver) {
1386
            return $this->typeResolver;
1387
        }
1388
1389
        $classResolver = $this->getDefaultTypeResolverClass();
1390
        $r = new ReflectionClass($classResolver);
1391
        $resolver = $r->newInstance();
1392
        $this->typeResolver = $resolver;
1393
1394
        return $this->typeResolver;
1395
    }
1396
1397
    /**
1398
     * Возвращает хранилище состояния workflow
1399
     *
1400
     * @return WorkflowStoreInterface
1401
     * @throws \OldTown\Workflow\Exception\StoreException
1402
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1403
     */
1404
    protected function getPersistence()
1405
    {
1406
        return $this->getConfiguration()->getWorkflowStore();
1407
    }
1408
1409
    /**
1410
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
1411
     * инициализирует ее.
1412
     *
1413
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
1414
     *
1415
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
1416
     *
1417
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1418
     */
1419
    public function getConfiguration()
1420
    {
1421
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
1422
1423
        if (!$config->isInitialized()) {
1424
            try {
1425
                $config->load(null);
1426
            } catch (FactoryException $e) {
1427
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
1428
                $this->getLog()->critical($errMsg, ['exception' => $e]);
1429
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1430
            }
1431
        }
1432
1433
        return $config;
1434
    }
1435
1436
    /**
1437
     * @return LoggerInterface
1438
     */
1439
    public function getLog()
1440
    {
1441
        return $this->log;
1442
    }
1443
1444
    /**
1445
     * @param LoggerInterface $log
1446
     *
1447
     * @return $this
1448
     * @throws InternalWorkflowException
1449
     */
1450
    public function setLog($log)
1451
    {
1452
        try {
1453
            LogFactory::validLogger($log);
1454
        } catch (\Exception $e) {
1455
            $errMsg = 'Ошибка при валидации логера';
1456
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1457
        }
1458
1459
1460
        $this->log = $log;
1461
1462
        return $this;
1463
    }
1464
1465
1466
    /**
1467
     * Get the workflow descriptor for the specified workflow name.
1468
     *
1469
     * @param string $workflowName The workflow name.
1470
     * @return WorkflowDescriptor
1471
     * @throws InternalWorkflowException
1472
     */
1473
    public function getWorkflowDescriptor($workflowName)
1474
    {
1475
        try {
1476
            return $this->getConfiguration()->getWorkflow($workflowName);
1477
        } catch (FactoryException $e) {
1478
            $errMsg = 'Ошибка при загрузке workflow';
1479
            $this->getLog()->error($errMsg, ['exception' => $e]);
1480
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1481
        }
1482
    }
1483
1484
1485
    /**
1486
     * Executes a special trigger-function using the context of the given workflow instance id.
1487
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
1488
     * @param integer $id The workflow instance id
1489
     * @param integer $triggerId The id of the special trigger-function
1490
     * @throws \OldTown\Workflow\Exception\WorkflowException
1491
     * @throws \OldTown\Workflow\Exception\StoreException
1492
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1493
     * @throws \OldTown\Workflow\Exception\FactoryException
1494
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1495
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1496
     */
1497
    public function executeTriggerFunction($id, $triggerId)
1498
    {
1499
        $store = $this->getPersistence();
1500
        $entry = $store->findEntry($id);
1501
1502
        if (null === $entry) {
1503
            $errMsg = sprintf(
1504
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
1505
                $triggerId,
1506
                $id
1507
            );
1508
            $this->getLog()->warning($errMsg);
1509
            return;
1510
        }
1511
1512
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1513
1514
        $ps = $store->getPropertySet($id);
1515
        $transientVars = $this->transientVarsFactory();
1516
1517
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
1518
1519
        $this->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
1520
    }
1521
1522
    /**
1523
     * @param $id
1524
     * @param $inputs
1525
     *
1526
     * @return array
1527
     * @throws \OldTown\Workflow\Exception\StoreException
1528
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1529
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1530
     * @throws \OldTown\Workflow\Exception\WorkflowException
1531
     * @throws \OldTown\Workflow\Exception\FactoryException
1532
     */
1533
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
1534
    {
1535
        try {
1536
            $store = $this->getPersistence();
1537
            $entry = $store->findEntry($id);
1538
1539
            if (null === $entry) {
1540
                $errMsg = sprintf(
1541
                    'Не существует экземпляра workflow c id %s',
1542
                    $id
1543
                );
1544
                throw new InvalidArgumentException($errMsg);
1545
            }
1546
1547
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
1548
                return [];
1549
            }
1550
1551
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1552
1553 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...
1554
                $errMsg = sprintf(
1555
                    'Не существует workflow c именем %s',
1556
                    $entry->getWorkflowName()
1557
                );
1558
                throw new InvalidArgumentException($errMsg);
1559
            }
1560
1561
            $l = [];
1562
            $ps = $store->getPropertySet($id);
1563
1564
            $transientVars = $inputs;
1565
            if (null === $transientVars) {
1566
                $transientVars = $this->transientVarsFactory();
1567
            }
1568
1569
            $currentSteps = $store->findCurrentSteps($id);
1570
1571
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
1572
1573
            $globalActions = $wf->getGlobalActions();
1574
1575
            foreach ($globalActions as $action) {
1576
                $restriction = $action->getRestriction();
1577
                $conditions = null;
1578
1579
                $transientVars['actionId'] = $action->getId();
1580
1581
                if (null !== $restriction) {
1582
                    $conditions = $restriction->getConditionsDescriptor();
1583
                }
1584
1585
                $flag = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
1586
                if ($flag) {
1587
                    $l[] = $action->getId();
1588
                }
1589
            }
1590
1591
1592 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...
1593
                $data = $this->getAvailableActionsForStep($wf, $step, $transientVars, $ps);
1594
                foreach ($data as $v) {
1595
                    $l[] = $v;
1596
                }
1597
            }
1598
            return array_unique($l);
1599
        } catch (\Exception $e) {
1600
            $errMsg = 'Ошибка проверки доступных действий';
1601
            $this->getLog()->error($errMsg, [$e]);
1602
        }
1603
1604
        return [];
1605
    }
1606
1607
    /**
1608
     * @param WorkflowDescriptor   $wf
1609
     * @param StepInterface        $step
1610
     * @param TransientVarsInterface                $transientVars
1611
     * @param PropertySetInterface $ps
1612
     *
1613
     * @return array
1614
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
1615
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1616
     * @throws \OldTown\Workflow\Exception\WorkflowException
1617
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1618
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1619
     */
1620
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
1621
    {
1622
        $l = [];
1623
        $s = $wf->getStep($step->getStepId());
1624
1625
        if (null === $s) {
1626
            $errMsg = sprintf(
1627
                'getAvailableActionsForStep вызван с не существующим id шага %s',
1628
                $step->getStepId()
1629
            );
1630
1631
            $this->getLog()->warning($errMsg);
1632
1633
            return $l;
1634
        }
1635
1636
        $actions  = $s->getActions();
1637
1638
        if (null === $actions || 0  === $actions->count()) {
1639
            return $l;
1640
        }
1641
1642
        foreach ($actions as $action) {
1643
            $restriction = $action->getRestriction();
1644
            $conditions = null;
1645
1646
            $transientVars['actionId'] = $action->getId();
1647
1648
1649
            if (null !== $restriction) {
1650
                $conditions = $restriction->getConditionsDescriptor();
1651
            }
1652
1653
            $f = $this->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
1654
                 && $this->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
1655
            if ($f) {
1656
                $l[] = $action->getId();
1657
            }
1658
        }
1659
1660
        return $l;
1661
    }
1662
1663
    /**
1664
     * @param ConfigurationInterface $configuration
1665
     *
1666
     * @return $this
1667
     */
1668
    public function setConfiguration(ConfigurationInterface $configuration)
1669
    {
1670
        $this->configuration = $configuration;
1671
1672
        return $this;
1673
    }
1674
1675
    /**
1676
     * Возвращает состояние для текущего экземпляра workflow
1677
     *
1678
     * @param integer $id id экземпляра workflow
1679
     * @return integer id текущего состояния
1680
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1681
     */
1682
    public function getEntryState($id)
1683
    {
1684
        try {
1685
            $store = $this->getPersistence();
1686
1687
            return $store->findEntry($id)->getState();
1688
        } catch (StoreException $e) {
1689
            $errMsg = sprintf(
1690
                'Ошибка при получение состояния экземпляра workflow c id# %s',
1691
                $id
1692
            );
1693
            $this->getLog()->error($errMsg, [$e]);
1694
        }
1695
1696
        return WorkflowEntryInterface::UNKNOWN;
1697
    }
1698
1699
    /**
1700
     * Returns a list of all steps that are completed for the given workflow instance id.
1701
     *
1702
     * @param integer $id The workflow instance id.
1703
     * @return StepInterface[] a List of Steps
1704
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1705
     */
1706 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...
1707
    {
1708
        try {
1709
            $store = $this->getPersistence();
1710
1711
            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 1711 which is incompatible with the return type declared by the interface OldTown\Workflow\Workflo...erface::getHistorySteps of type OldTown\Workflow\Spi\StepInterface[].
Loading history...
1712
        } catch (StoreException $e) {
1713
            $errMsg = sprintf(
1714
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
1715
                $id
1716
            );
1717
            $this->getLog()->error($errMsg, [$e]);
1718
        }
1719
1720
        return [];
1721
    }
1722
1723
    /**
1724
     * Настройки хранилища
1725
     *
1726
     * @return array
1727
     *
1728
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1729
     */
1730
    public function getPersistenceProperties()
1731
    {
1732
        return $this->getConfiguration()->getPersistenceArgs();
1733
    }
1734
1735
1736
    /**
1737
     * Get the PropertySet for the specified workflow instance id.
1738
     * @param integer $id The workflow instance id.
1739
     * @return PropertySetInterface
1740
     *
1741
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1742
     */
1743 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...
1744
    {
1745
        $ps = null;
1746
1747
        try {
1748
            $ps = $this->getPersistence()->getPropertySet($id);
1749
        } catch (StoreException $e) {
1750
            $errMsg = sprintf(
1751
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1752
                $id
1753
            );
1754
            $this->getLog()->error($errMsg, [$e]);
1755
        }
1756
1757
        return $ps;
1758
    }
1759
1760
    /**
1761
     * @return \String[]
1762
     *
1763
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1764
     */
1765
    public function getWorkflowNames()
1766
    {
1767
        try {
1768
            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...
1769
        } catch (FactoryException $e) {
1770
            $errMsg = 'Ошибка при получение имен workflow';
1771
            $this->getLog()->error($errMsg, [$e]);
1772
        }
1773
1774
        return [];
1775
    }
1776
1777
    /**
1778
     * @param TypeResolverInterface $typeResolver
1779
     *
1780
     * @return $this
1781
     */
1782
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1783
    {
1784
        $this->typeResolver = $typeResolver;
1785
1786
        return $this;
1787
    }
1788
1789
1790
    /**
1791
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1792
     * @param integer $id id the workflow instance id.
1793
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1794
     * @return array  A List of permissions specified currently (a permission is a string name).
1795
     *
1796
     */
1797
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1798
    {
1799
        try {
1800
            $store = $this->getPersistence();
1801
            $entry = $store->findEntry($id);
1802
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1803
1804
            $ps = $store->getPropertySet($id);
1805
1806
            if (null === $inputs) {
1807
                $inputs = $this->transientVarsFactory();
1808
            }
1809
            $transientVars = $inputs;
1810
1811
            $currentSteps = $store->findCurrentSteps($id);
1812
1813
            try {
1814
                $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1815
            } catch (\Exception $e) {
1816
                $errMsg = sprintf(
1817
                    'Внутреннея ошибка: %s',
1818
                    $e->getMessage()
1819
                );
1820
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1821
            }
1822
1823
1824
            $s = [];
1825
1826
            foreach ($currentSteps as $step) {
1827
                $stepId = $step->getStepId();
1828
1829
                $xmlStep = $wf->getStep($stepId);
1830
1831
                $securities = $xmlStep->getPermissions();
1832
1833
                foreach ($securities as $security) {
1834
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1835
                    if (null !== $security->getRestriction() && $this->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1836
                        $s[$security->getName()] = $security->getName();
1837
                    }
1838
                }
1839
            }
1840
1841
            return $s;
1842
        } catch (\Exception $e) {
1843
            $errMsg = sprintf(
1844
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1845
                $id
1846
            );
1847
            $this->getLog()->error($errMsg, [$e]);
1848
        }
1849
1850
        return [];
1851
    }
1852
1853
1854
    /**
1855
     * Get the name of the specified workflow instance.
1856
     *
1857
     * @param integer $id the workflow instance id.
1858
     * @return string
1859
     *
1860
     * @throws \OldTown\Workflow\Exception\StoreException
1861
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1862
     */
1863
    public function getWorkflowName($id)
1864
    {
1865
        try {
1866
            $store = $this->getPersistence();
1867
            $entry = $store->findEntry($id);
1868
1869
            if (null !== $entry) {
1870
                return $entry->getWorkflowName();
1871
            }
1872
        } catch (FactoryException $e) {
1873
            $errMsg = sprintf(
1874
                'Ошибка при получение имен workflow для инстанса с id # %s',
1875
                $id
1876
            );
1877
            $this->getLog()->error($errMsg, [$e]);
1878
        }
1879
1880
        return null;
1881
    }
1882
1883
    /**
1884
     * Удаляет workflow
1885
     *
1886
     * @param string $workflowName
1887
     *
1888
     * @return bool
1889
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1890
     */
1891
    public function removeWorkflowDescriptor($workflowName)
1892
    {
1893
        return $this->getConfiguration()->removeWorkflow($workflowName);
1894
    }
1895
1896
    /**
1897
     * @param                    $workflowName
1898
     * @param WorkflowDescriptor $descriptor
1899
     * @param                    $replace
1900
     *
1901
     * @return bool
1902
     *
1903
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1904
     */
1905
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1906
    {
1907
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1908
1909
        return $success;
1910
    }
1911
1912
1913
    /**
1914
     * Query the workflow store for matching instances
1915
     *
1916
     * @param WorkflowExpressionQuery $query
1917
     *
1918
     * @return array
1919
     *
1920
     * @throws \OldTown\Workflow\Exception\WorkflowException
1921
     * @throws \OldTown\Workflow\Exception\StoreException
1922
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1923
1924
     */
1925
    public function query(WorkflowExpressionQuery $query)
1926
    {
1927
        return $this->getPersistence()->query($query);
1928
    }
1929
1930
    /**
1931
     * @return string
1932
     */
1933
    public function getDefaultTypeResolverClass()
1934
    {
1935
        return $this->defaultTypeResolverClass;
1936
    }
1937
1938
    /**
1939
     * @param string $defaultTypeResolverClass
1940
     *
1941
     * @return $this
1942
     */
1943
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1944
    {
1945
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1946
1947
        return $this;
1948
    }
1949
1950
1951
    /**
1952
     * @param ConditionsDescriptor $descriptor
1953
     * @param TransientVarsInterface $transientVars
1954
     * @param PropertySetInterface $ps
1955
     * @param                      $currentStepId
1956
     *
1957
     * @return bool
1958
     *
1959
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1960
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1961
     * @throws \OldTown\Workflow\Exception\WorkflowException
1962
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1963
     */
1964
    protected function passesConditionsByDescriptor(ConditionsDescriptor $descriptor = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
1965
    {
1966
        if (null === $descriptor) {
1967
            return true;
1968
        }
1969
1970
        $type = $descriptor->getType();
1971
        $conditions = $descriptor->getConditions();
1972
        $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 1971 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...
1973
1974
        return $passesConditions;
1975
    }
1976
1977
    /**
1978
     * @param string $conditionType
1979
     * @param SplObjectStorage $conditions
1980
     * @param TransientVarsInterface $transientVars
1981
     * @param PropertySetInterface $ps
1982
     * @param integer $currentStepId
1983
     *
1984
     * @return bool
1985
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
1986
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
1987
     * @throws \OldTown\Workflow\Exception\WorkflowException
1988
     * @throws \OldTown\Workflow\Exception\InvalidActionException
1989
     */
1990
    protected function passesConditionsWithType($conditionType, SplObjectStorage $conditions = null, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
1991
    {
1992
        if (null === $conditions) {
1993
            return true;
1994
        }
1995
1996
        if (0 === $conditions->count()) {
1997
            return true;
1998
        }
1999
2000
        $and = strtoupper($conditionType) === 'AND';
2001
        $or = !$and;
2002
2003
        foreach ($conditions as $descriptor) {
2004
            if ($descriptor instanceof ConditionsDescriptor) {
2005
                $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...
2006
            } else {
2007
                $result = $this->passesCondition($descriptor, $transientVars, $ps, $currentStepId);
2008
            }
2009
2010
            if ($and && !$result) {
2011
                return false;
2012
            } elseif ($or && $result) {
2013
                return true;
2014
            }
2015
        }
2016
2017
        if ($and) {
2018
            return true;
2019
        }
2020
2021
        return false;
2022
    }
2023
2024
    /**
2025
     * @param ConditionDescriptor $conditionDesc
2026
     * @param TransientVarsInterface $transientVars
2027
     * @param PropertySetInterface $ps
2028
     * @param integer $currentStepId
2029
     *
2030
     * @return boolean
2031
     *
2032
     * @throws \OldTown\Workflow\Exception\InternalWorkflowException
2033
     * @throws \OldTown\Workflow\Exception\WorkflowException
2034
     */
2035
    protected function passesCondition(ConditionDescriptor $conditionDesc, TransientVarsInterface $transientVars, PropertySetInterface $ps, $currentStepId)
2036
    {
2037
        $type = $conditionDesc->getType();
2038
2039
        $argsOriginal = $conditionDesc->getArgs();
2040
2041
2042
        $args = [];
2043 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...
2044
            $translateValue = $this->getConfiguration()->getVariableResolver()->translateVariables($value, $transientVars, $ps);
2045
            $args[$key] = $translateValue;
2046
        }
2047
2048
        if (-1 !== $currentStepId) {
2049
            $stepId = array_key_exists('stepId', $args) ? (integer)$args['stepId'] : null;
2050
2051
            if (null !== $stepId && -1 === $stepId) {
2052
                $args['stepId'] = $currentStepId;
2053
            }
2054
        }
2055
2056
        $condition = $this->getResolver()->getCondition($type, $args);
2057
2058
        if (null === $condition) {
2059
            $this->context->setRollbackOnly();
2060
            $errMsg = 'Огибка при загрузки условия';
2061
            throw new WorkflowException($errMsg);
2062
        }
2063
2064
        try {
2065
            $passed = $condition->passesCondition($transientVars, $args, $ps);
2066
2067
            if ($conditionDesc->isNegate()) {
2068
                $passed = !$passed;
2069
            }
2070
        } catch (\Exception $e) {
2071
            $this->context->setRollbackOnly();
2072
2073
            $errMsg = sprintf(
2074
                'Ошбика при выполнение условия %s',
2075
                get_class($condition)
2076
            );
2077
2078
            throw new WorkflowException($errMsg, $e->getCode(), $e);
2079
        }
2080
2081
        return $passed;
2082
    }
2083
}
2084