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 ( b6a35f...04294e )
by Андрей
05:37
created

AbstractWorkflow::initLoger()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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