GitHub Access Token became invalid

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

AbstractWorkflow::getAvailableAutoActionsForStep()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.5923

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 21
ccs 8
cts 12
cp 0.6667
rs 9.0534
cc 4
eloc 12
nc 3
nop 4
crap 4.5923
1
<?php
2
/**
3
 * @link https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow;
7
8
use OldTown\Log\LogFactory;
9
use OldTown\PropertySet\PropertySetInterface;
10
use OldTown\PropertySet\PropertySetManager;
11
use OldTown\Workflow\Config\ConfigurationInterface;
12
use OldTown\Workflow\Config\DefaultConfiguration;
13
use OldTown\Workflow\Exception\FactoryException;
14
use OldTown\Workflow\Exception\InternalWorkflowException;
15
use OldTown\Workflow\Exception\InvalidActionException;
16
use OldTown\Workflow\Exception\InvalidArgumentException;
17
use OldTown\Workflow\Exception\InvalidEntryStateException;
18
use OldTown\Workflow\Exception\InvalidInputException;
19
use OldTown\Workflow\Exception\InvalidRoleException;
20
use OldTown\Workflow\Exception\StoreException;
21
use OldTown\Workflow\Exception\WorkflowException;
22
use OldTown\Workflow\Loader\ActionDescriptor;
23
use OldTown\Workflow\Loader\ConditionDescriptor;
24
use OldTown\Workflow\Loader\ConditionsDescriptor;
25
use OldTown\Workflow\Loader\FunctionDescriptor;
26
use OldTown\Workflow\Loader\PermissionDescriptor;
27
use OldTown\Workflow\Loader\RegisterDescriptor;
28
use OldTown\Workflow\Loader\ResultDescriptor;
29
use OldTown\Workflow\Loader\ValidatorDescriptor;
30
use OldTown\Workflow\Loader\WorkflowDescriptor;
31
use OldTown\Workflow\Query\WorkflowExpressionQuery;
32
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
33
use OldTown\Workflow\Spi\StepInterface;
34
use OldTown\Workflow\Spi\WorkflowEntryInterface;
35
use OldTown\Workflow\Spi\WorkflowStoreInterface;
36
use OldTown\Workflow\TransientVars\TransientVarsInterface;
37
use Psr\Log\LoggerInterface;
38
use Traversable;
39
use SplObjectStorage;
40
use DateTime;
41
use OldTown\Workflow\TransientVars\BaseTransientVars;
42
use ReflectionClass;
43
use ArrayObject;
44
45
46
/**
47
 * Class AbstractWorkflow
48
 *
49
 * @package OldTown\Workflow
50
 */
51
abstract class  AbstractWorkflow implements WorkflowInterface
52
{
53
    /**
54
     * @var string
55
     */
56
    const CURRENT_STEPS = 'currentSteps';
57
58
    /**
59
     * @var string
60
     */
61
    const HISTORY_STEPS = 'historySteps';
62
63
    /**
64
     * @var WorkflowContextInterface
65
     */
66
    protected $context;
67
68
    /**
69
     * @var ConfigurationInterface
70
     */
71
    protected $configuration;
72
73
    /**
74
     *
75
     * @var array
76
     */
77
    protected $stateCache = [];
78
79
    /**
80
     * @var TypeResolverInterface
81
     */
82
    protected $typeResolver;
83
84
    /**
85
     * Логер
86
     *
87
     * @var LoggerInterface
88
     */
89
    protected $log;
90
91
    /**
92
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
93
     *
94
     * @var string
95
     */
96
    protected $defaultTypeResolverClass = TypeResolver::class;
97
98
    /**
99
     * AbstractWorkflow constructor.
100
     *
101
     * @throws InternalWorkflowException
102
     */
103 19
    public function __construct()
104
    {
105 19
        $this->initLoger();
106 19
    }
107
108
    /**
109
     * Инициализация системы логирования
110
     *
111
     * @throws InternalWorkflowException
112
     */
113 19
    protected function initLoger()
114
    {
115
        try {
116 19
            $this->log = LogFactory::getLog();
117 19
        } catch (\Exception $e) {
118
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
119
        }
120 19
    }
121
122
    /**
123
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
124
     * Workflow может быть инициализированно только один раз
125
     *
126
     * @param string $workflowName Имя workflow
127
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
128
     * @param TransientVarsInterface $inputs Данные введеные пользователем
129
     *
130
     * @return integer
131
     *
132
     * @throws InternalWorkflowException
133
     * @throws InvalidActionException
134
     * @throws InvalidRoleException
135
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
136
     * @throws InvalidArgumentException
137
     * @throws WorkflowException
138
     */
139 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
140
    {
141
        try {
142 19
            $initialAction = (integer)$initialAction;
143
144 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
145
146 19
            $store = $this->getPersistence();
147
148 19
            $entry = $store->createEntry($workflowName);
149
150 19
            $ps = $store->getPropertySet($entry->getId());
151
152
153 19
            if (null === $inputs) {
154
                $inputs = $this->transientVarsFactory();
155
            }
156 19
            $transientVars = $inputs;
157 19
            $inputs = clone $transientVars;
158
159 19
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
160
161 19
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
162 1
                $this->context->setRollbackOnly();
163 1
                $errMsg = 'You are restricted from initializing this workflow';
164 1
                throw new InvalidRoleException($errMsg);
165
            }
166
167 18
            $action = $wf->getInitialAction($initialAction);
168
169 18
            if (null === $action) {
170
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
171
                throw new InvalidActionException($errMsg);
172
            }
173
174 18
            $currentSteps = new SplObjectStorage();
175 18
            $this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
176
177 16
            $entryId = $entry->getId();
178 19
        } catch (WorkflowException $e) {
179
            $this->context->setRollbackOnly();
180
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
181
        }
182
183
184
        // now clone the memory PS to the real PS
185
        //PropertySetManager.clone(ps, store.getPropertySet(entryId));
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
412
//                $theResults = [];
413
//                foreach ($results as $result) {
414
//                    $theResults[] = $result;
415
//                }
416
417
                    foreach ($results as $resultDescriptor) {
418
                        $moveToHistoryStep = null;
419
420
                        if ($moveFirst) {
421
                            $moveToHistoryStep = $step;
422
                        }
423
424
                        $previousIds = [];
425
426
                        if (null !== $step) {
427
                            $previousIds[] = $step->getStepId();
428
                        }
429
430
                        $this->createNewCurrentStep($resultDescriptor, $entry, $store, $action->getId(), $moveToHistoryStep, $previousIds, $transientVars, $ps);
431
                        $moveFirst = false;
432
                    }
433
                }
434
435
436
                foreach ($splitPostFunctions as $function) {
437
                    $this->executeFunction($function, $transientVars, $ps);
438
                }
439 16
            } elseif (null !== $join && 0 !== $join) {
440
                $joinDesc = $wf->getJoin($join);
441
                $oldStatus = $theResult->getOldStatus();
442
                $caller = $this->context->getCaller();
443
                if (null !== $step) {
444
                    $step = $store->markFinished($step, $action->getId(), new DateTime(), $oldStatus, $caller);
445
                } else {
446
                    $errMsg = 'Invalid step';
447
                    throw new InternalWorkflowException($errMsg);
448
                }
449
450
451
                $store->moveToHistory($step);
452
453
                /** @var StepInterface[] $joinSteps */
454
                $joinSteps = [];
455
                $joinSteps[] = $step;
456
457
                $joinSteps = $this->buildJoinsSteps($currentSteps, $step, $wf, $join, $joinSteps);
0 ignored issues
show
Documentation introduced by
$currentSteps is of type object<SplObjectStorage>, but the function expects a array<integer,object<Old...low\Spi\StepInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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

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

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

    return array();
}

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

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

Loading history...
462
463
464
                $jn = new JoinNodes($joinSteps);
465
                $transientVars['jn'] = $jn;
466
467
468
                if ($this->passesConditionsWithType(null, $joinDesc->getConditions(), $transientVars, $ps, 0)) {
469
                    $joinResult = $joinDesc->getResult();
470
471
                    $joinResultValidators = $joinResult->getValidators();
472
                    if ($joinResultValidators->count() > 0) {
473
                        $this->verifyInputs($joinResultValidators, $transientVars, $ps);
474
                    }
475
476
                    foreach ($joinResult->getPreFunctions() as $function) {
477
                        $this->executeFunction($function, $transientVars, $ps);
478
                    }
479
480
                    $previousIds = [];
481
                    $i = 1;
482
483
                    foreach ($joinSteps as  $currentJoinStep) {
484
                        if (!$historySteps->contains($currentJoinStep) && $currentJoinStep->getId() !== $step->getId()) {
485
                            $store->moveToHistory($step);
486
                        }
487
488 16
                        $previousIds[$i] = $currentJoinStep->getId();
489
                    }
490
491
                    if (!$action->isFinish()) {
492
                        $previousIds[0] = $step->getId();
493
                        $theResult = $joinDesc->getResult();
494
495
                        $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), null, $previousIds, $transientVars, $ps);
496
                    }
497
498
                    foreach ($joinResult->getPostFunctions() as $function) {
499
                        $this->executeFunction($function, $transientVars, $ps);
500
                    }
501
                }
502
            } else {
503 16
                $previousIds = [];
504
505 16
                if (null !== $step) {
506 5
                    $previousIds[] = $step->getId();
507 5
                }
508
509 16
                if (!$action->isFinish()) {
510 16
                    $this->createNewCurrentStep($theResult, $entry, $store, $action->getId(), $step, $previousIds, $transientVars, $ps);
511 16
                }
512
            }
513
514 16
            if ($extraPostFunctions && $extraPostFunctions->count() > 0) {
515 2
                foreach ($extraPostFunctions as $function) {
516 2
                    $this->executeFunction($function, $transientVars, $ps);
517 2
                }
518 2
            }
519
520 16
            if (WorkflowEntryInterface::COMPLETED !== $entry->getState() && null !== $wf->getInitialAction($action->getId())) {
521 16
                $this->changeEntryState($entry->getId(), WorkflowEntryInterface::ACTIVATED);
522 16
            }
523
524 16
            if ($action->isFinish()) {
525
                $this->completeEntry($action, $entry->getId(), $this->getCurrentSteps($entry->getId()), WorkflowEntryInterface::COMPLETED);
526
                return true;
527
            }
528
529 16
            $availableAutoActions = $this->getAvailableAutoActions($entry->getId(), $inputs);
530
531 16
            if (count($availableAutoActions) > 0) {
532
                $this->doAction($entry->getId(), $availableAutoActions[0], $inputs);
533
            }
534
535 16
            return false;
536 3
        } catch (\Exception $e) {
537 3
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
538
        }
539
    }
540
541
    /**
542
     * Подготавливает данные о шагах используемых в объеденение
543
     *
544
     * @param StepInterface[]    $steps
545
     * @param StepInterface      $step
546
     * @param WorkflowDescriptor $wf
547
     * @param integer            $join
548
     *
549
     * @param array              $joinSteps
550
     *
551
     * @return array
552
     *
553
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
554
     */
555
    protected function buildJoinsSteps($steps, StepInterface $step, WorkflowDescriptor $wf, $join, array $joinSteps = [])
556
    {
557
        foreach ($steps as $currentStep) {
558
            if ($currentStep->getId() !== $step->getId()) {
559
                $stepDesc = $wf->getStep($currentStep->getStepId());
560
561
                if ($stepDesc->resultsInJoin($join)) {
562
                    $joinSteps[] = $currentStep;
563
                }
564
            }
565
        }
566
567
        return $joinSteps;
568
    }
569
570
    /**
571
     * @param       $id
572
     * @param TransientVarsInterface $inputs
573
     *
574
     * @return array
575
     */
576 19
    protected function getAvailableAutoActions($id, TransientVarsInterface $inputs)
577
    {
578
        try {
579 16
            $store = $this->getPersistence();
580 16
            $entry = $store->findEntry($id);
581
582 16
            if (null === $entry) {
583
                $errMsg = sprintf(
584
                    'Нет сущности workflow c id %s',
585
                    $id
586
                );
587
                throw new InvalidArgumentException($errMsg);
588 19
            }
589
590
591 16
            if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
592
                $logMsg = sprintf('--> состояние %s', $entry->getState());
593
                $this->getLog()->debug($logMsg);
594
                return [0];
595
            }
596
597 16
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
598
599 16
            $l = [];
600 16
            $ps = $store->getPropertySet($id);
601 16
            $transientVars = $inputs;
602 16
            $currentSteps = $store->findCurrentSteps($id);
603
604 16
            $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
605
606 16
            $globalActions = $wf->getGlobalActions();
607
608 16
            $l = $this->buildListIdsAvailableActions($globalActions, $transientVars, $ps, $l);
0 ignored issues
show
Bug introduced by
It seems like $globalActions defined by $wf->getGlobalActions() on line 606 can also be of type object<SplObjectStorage>; however, OldTown\Workflow\Abstrac...stIdsAvailableActions() does only seem to accept array<integer,object<Old...ader\ActionDescriptor>>, 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...
609
610 16 View Code Duplication
            foreach ($currentSteps as $step) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
611 16
                $availableAutoActionsForStep = $this->getAvailableAutoActionsForStep($wf, $step, $transientVars, $ps);
612 16
                foreach ($availableAutoActionsForStep as $v) {
613
                    $l[] = $v;
614 16
                }
615 16
            }
616
617 16
            $l = array_unique($l);
618
619 16
            return $l;
620
        } catch (\Exception $e) {
621
            $errMsg = 'Ошибка при проверке доступных действий';
622
            $this->getLog()->error($errMsg, [$e]);
623
        }
624
625
        return [];
626
    }
627
628
    /**
629
     * Подготавливает список id действий в workflow
630
     *
631
     * @param ActionDescriptor[]     $actions
632
     * @param TransientVarsInterface $transientVars
633
     * @param PropertySetInterface   $ps
634
     * @param array                  $storage
635
     *
636
     * @return array
637
     *
638
     * @throws InternalWorkflowException
639
     * @throws WorkflowException
640
     */
641 16
    protected function buildListIdsAvailableActions($actions, TransientVarsInterface $transientVars, PropertySetInterface $ps, array $storage = [])
642
    {
643 16
        foreach ($actions as $action) {
644 16
            $transientVars['actionId'] = $action->getId();
645
646 16
            if ($action->getAutoExecute() && $this->isActionAvailable($action, $transientVars, $ps, 0)) {
647
                $storage[] = $action->getId();
648
            }
649 16
        }
650
651 16
        return $storage;
652
    }
653
654
    /**
655
     * @param WorkflowDescriptor   $wf
656
     * @param StepInterface        $step
657
     * @param TransientVarsInterface                $transientVars
658
     * @param PropertySetInterface $ps
659
     *
660
     * @return array
661
     *
662
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
663
     * @throws InternalWorkflowException
664
     * @throws WorkflowException
665
     */
666 16
    protected function getAvailableAutoActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
667
    {
668 16
        $l = [];
669 16
        $s = $wf->getStep($step->getStepId());
670
671 16
        if (null === $s) {
672
            $msg = sprintf('getAvailableAutoActionsForStep вызвана с несуществующим id %s', $step->getStepId());
673
            $this->getLog()->debug($msg);
674
            return $l;
675
        }
676
677
678 16
        $actions = $s->getActions();
679 16
        if (null === $actions || 0 === $actions->count()) {
680
            return $l;
681
        }
682
683 16
        $l = $this->buildListIdsAvailableActions($actions, $transientVars, $ps, $l);
0 ignored issues
show
Bug introduced by
It seems like $actions defined by $s->getActions() on line 678 can also be of type object<SplObjectStorage>; however, OldTown\Workflow\Abstrac...stIdsAvailableActions() does only seem to accept array<integer,object<Old...ader\ActionDescriptor>>, 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...
684
685 16
        return $l;
686
    }
687
688
    /**
689
     * @param ActionDescriptor $action
690
     * @param                  $id
691
     * @param array|Traversable $currentSteps
692
     * @param                  $state
693
     *
694
     * @return void
695
     *
696
     * @throws InvalidArgumentException
697
     * @throws InternalWorkflowException
698
     */
699
    protected function completeEntry(ActionDescriptor $action = null, $id, $currentSteps, $state)
700
    {
701
        $this->validateIterateData($currentSteps);
702
703
704
        $this->getPersistence()->setEntryState($id, $state);
705
706
        $oldStatus = null !== $action ? $action->getUnconditionalResult()->getOldStatus() : 'Finished';
707
        $actionIdValue = null !== $action ? $action->getId() : -1;
708
        foreach ($currentSteps as $step) {
709
            $this->getPersistence()->markFinished($step, $actionIdValue, new DateTime(), $oldStatus, $this->context->getCaller());
710
            $this->getPersistence()->moveToHistory($step);
711
        }
712
    }
713
    /**
714
     * @param ResultDescriptor       $theResult
715
     * @param WorkflowEntryInterface $entry
716
     * @param WorkflowStoreInterface $store
717
     * @param integer                $actionId
718
     * @param StepInterface          $currentStep
719
     * @param array                  $previousIds
720
     * @param TransientVarsInterface                  $transientVars
721
     * @param PropertySetInterface   $ps
722
     *
723
     * @return StepInterface
724
     *
725
     * @throws InternalWorkflowException
726
     * @throws StoreException
727
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
728
     * @throws WorkflowException
729
     */
730 16
    protected function createNewCurrentStep(
731
        ResultDescriptor $theResult,
732
        WorkflowEntryInterface $entry,
733
        WorkflowStoreInterface $store,
734
        $actionId,
735
        StepInterface $currentStep = null,
736
        array $previousIds = [],
737
        TransientVarsInterface $transientVars,
738
        PropertySetInterface $ps
739
    ) {
740
        try {
741 16
            $nextStep = $theResult->getStep();
742
743 16
            if (-1 === $nextStep) {
744
                if (null !== $currentStep) {
745
                    $nextStep = $currentStep->getStepId();
746
                } else {
747
                    $errMsg = 'Неверный аргумент. Новый шаг является таким же как текущий. Но текущий шаг не указан';
748
                    throw new StoreException($errMsg);
749
                }
750
            }
751
752 16
            $owner = $theResult->getOwner();
753
754 16
            $logMsg = sprintf(
755 16
                'Результат: stepId=%s, status=%s, owner=%s, actionId=%s, currentStep=%s',
756 16
                $nextStep,
757 16
                $theResult->getStatus(),
758 16
                $owner,
759 16
                $actionId,
760 16
                null !== $currentStep ? $currentStep->getId() : 0
761 16
            );
762 16
            $this->getLog()->debug($logMsg);
763
764 16
            $variableResolver = $this->getConfiguration()->getVariableResolver();
765
766 16
            if (null !== $owner) {
767
                $o = $variableResolver->translateVariables($owner, $transientVars, $ps);
768
                $owner = null !== $o ? (string)$o : null;
769
            }
770
771
772 16
            $oldStatus = $theResult->getOldStatus();
773 16
            $oldStatus = (string)$variableResolver->translateVariables($oldStatus, $transientVars, $ps);
774
775 16
            $status = $theResult->getStatus();
776 16
            $status = (string)$variableResolver->translateVariables($status, $transientVars, $ps);
777
778
779 16
            if (null !== $currentStep) {
780 5
                $store->markFinished($currentStep, $actionId, new DateTime(), $oldStatus, $this->context->getCaller());
781 5
                $store->moveToHistory($currentStep);
782 5
            }
783
784 16
            $startDate = new DateTime();
785 16
            $dueDate = null;
786
787 16
            $theResultDueDate = (string)$theResult->getDueDate();
788 16
            $theResultDueDate = trim($theResultDueDate);
789 16
            if (strlen($theResultDueDate) > 0) {
790
                $dueDateObject = $variableResolver->translateVariables($theResultDueDate, $transientVars, $ps);
791
792
                if ($dueDateObject instanceof DateTime) {
793
                    $dueDate = $dueDateObject;
794
                } elseif (is_string($dueDateObject)) {
795
                    $dueDate = new DateTime($dueDate);
796
                } elseif (is_numeric($dueDateObject)) {
797
                    $dueDate = DateTime::createFromFormat('U', $dueDateObject);
798
                    if (false === $dueDate) {
799
                        $errMsg = 'Invalid due date conversion';
800
                        throw new Exception\InternalWorkflowException($errMsg);
801
                    }
802
                }
803
            }
804
805 16
            $newStep = $store->createCurrentStep($entry->getId(), $nextStep, $owner, $startDate, $dueDate, $status, $previousIds);
806 16
            $transientVars['createdStep'] =  $newStep;
807
808 16
            if (null === $currentStep && 0 === count($previousIds)) {
809 16
                $currentSteps = [];
810 16
                $currentSteps[] = $newStep;
811 16
                $transientVars['currentSteps'] =  $currentSteps;
812 16
            }
813
814 16
            if (! $transientVars->offsetExists('descriptor')) {
815
                $errMsg = 'Ошибка при получение дескриптора workflow из transientVars';
816
                throw new InternalWorkflowException($errMsg);
817
            }
818
819
            /** @var WorkflowDescriptor $descriptor */
820 16
            $descriptor = $transientVars['descriptor'];
821 16
            $step = $descriptor->getStep($nextStep);
822
823 16
            if (null === $step) {
824
                $errMsg = sprintf('Шаг #%s не найден', $nextStep);
825
                throw new WorkflowException($errMsg);
826
            }
827
828 16
            $preFunctions = $step->getPreFunctions();
829
830 16
            foreach ($preFunctions as $function) {
831
                $this->executeFunction($function, $transientVars, $ps);
832 16
            }
833 16
        } catch (WorkflowException $e) {
834
            $this->context->setRollbackOnly();
835
            /** @var WorkflowException $e */
836
            throw $e;
837
        }
838 16
    }
839
840
    /**
841
     * Создает хранилище переменных
842
     *
843
     * @param $class
844
     *
845
     * @return TransientVarsInterface
846
     */
847
    protected function transientVarsFactory($class = BaseTransientVars::class)
848
    {
849
        $r = new \ReflectionClass($class);
850
        return $r->newInstance();
851
    }
852
853
    /**
854
     *
855
     *
856
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
857
     *
858
     * @param integer $entryId id запущенного процесса workflow
859
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
860
     * @param TransientVarsInterface $inputs Входные данные для перехода
861
     *
862
     * @return void
863
     *
864
     * @throws WorkflowException
865
     * @throws InvalidActionException
866
     * @throws InvalidArgumentException
867
     * @throws InternalWorkflowException
868
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
869
     */
870 8
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
871
    {
872 8
        $actionId = (integer)$actionId;
873 8
        if (null === $inputs) {
874
            $inputs = $this->transientVarsFactory();
875
        }
876 8
        $transientVars = $inputs;
877 8
        $inputs = clone $transientVars;
878
879 8
        $store = $this->getPersistence();
880 8
        $entry = $store->findEntry($entryId);
881
882 8
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
883
            return;
884
        }
885
886 8
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
887
888 8
        $currentSteps = $store->findCurrentSteps($entryId);
889 8
        $action = null;
890
891 8
        $ps = $store->getPropertySet($entryId);
892
893 8
        $this->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
894
895
896 8
        $validAction = false;
897
898 8
        foreach ($wf->getGlobalActions() as $actionDesc) {
899
            if ($actionId === $actionDesc->getId()) {
900
                $action = $actionDesc;
901
902
                if ($this->isActionAvailable($action, $transientVars, $ps, 0)) {
903
                    $validAction = true;
904
                }
905
            }
906 8
        }
907
908
909 8
        foreach ($currentSteps as $step) {
910 8
            $s = $wf->getStep($step->getStepId());
911
912 8
            foreach ($s->getActions() as $actionDesc) {
913 8
                if (!$actionDesc instanceof ActionDescriptor) {
914
                    $errMsg = 'Invalid action descriptor';
915
                    throw new InternalWorkflowException($errMsg);
916
                }
917
918 8
                if ($actionId === $actionDesc->getId()) {
919 8
                    $action = $actionDesc;
920
921 8
                    if ($this->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
922 6
                        $validAction = true;
923 6
                    }
924 8
                }
925 8
            }
926 8
        }
927
928
929 8
        if (!$validAction) {
930 2
            $errMsg = sprintf(
931 2
                'Action %s is invalid',
932
                $actionId
933 2
            );
934 2
            throw new InvalidActionException($errMsg);
935
        }
936
937
938
        try {
939 6
            if ($this->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps)) {
0 ignored issues
show
Documentation introduced by
$currentSteps is of type array<integer,object<Old...low\Spi\StepInterface>>, but the function expects a object<SplObjectStorage>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
940
                $this->checkImplicitFinish($action, $entryId);
941
            }
942 6
        } catch (WorkflowException $e) {
943
            $this->context->setRollbackOnly();
944
            /** @var  WorkflowException $e*/
945
            throw $e;
946
        }
947 5
    }
948
949
    /**
950
     * @param ActionDescriptor $action
951
     * @param                  $id
952
     *
953
     * @return void
954
     *
955
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
956
     * @throws InvalidArgumentException
957
     * @throws InternalWorkflowException
958
     */
959
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
960
    {
961
        $store = $this->getPersistence();
962
        $entry = $store->findEntry($id);
963
964
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
965
966
        $currentSteps = $store->findCurrentSteps($id);
967
968
        $isCompleted = $wf->getGlobalActions()->count() === 0;
969
970
        foreach ($currentSteps as $step) {
971
            if ($isCompleted) {
972
                break;
973
            }
974
975
            $stepDes = $wf->getStep($step->getStepId());
976
977
            if ($stepDes->getActions()->count() > 0) {
978
                $isCompleted = true;
979
            }
980
        }
981
982
        if ($isCompleted) {
983
            $this->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
984
        }
985
    }
986
987
    /**
988
     *
989
     * Check if the state of the specified workflow instance can be changed to the new specified one.
990
     *
991
     * @param integer $id The workflow instance id.
992
     * @param integer $newState The new state id.
993
     *
994
     * @return boolean true if the state of the workflow can be modified, false otherwise.
995
     *
996
     * @throws InternalWorkflowException
997
     */
998 16
    public function canModifyEntryState($id, $newState)
999
    {
1000 16
        $store = $this->getPersistence();
1001 16
        $entry = $store->findEntry($id);
1002
1003 16
        $currentState = $entry->getState();
1004
1005 16
        $result = false;
1006
        try {
1007
            switch ($newState) {
1008 16
                case WorkflowEntryInterface::COMPLETED: {
1009
                    if (WorkflowEntryInterface::ACTIVATED === $currentState) {
1010
                        $result = true;
1011
                    }
1012
                    break;
1013
                }
1014
1015
                //@TODO Разобраться с бизнес логикой. Может быть нужно добавить break
1016
                /** @noinspection PhpMissingBreakStatementInspection */
1017 16
                case WorkflowEntryInterface::CREATED: {
1018
                    $result = false;
1019
                }
1020 16 View Code Duplication
                case WorkflowEntryInterface::ACTIVATED: {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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