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.

WorkflowDescriptor   F
last analyzed

Complexity

Total Complexity 110

Size/Duplication

Total Lines 980
Duplicated Lines 11.33 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 110
c 3
b 0
f 0
lcom 1
cbo 16
dl 111
loc 980
ccs 452
cts 452
cp 1
rs 1.263

34 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 2
A getName() 0 4 1
A setName() 0 6 1
C validate() 0 70 9
B validateDtd() 0 37 3
F init() 49 122 17
A getGlobalConditions() 0 4 1
A addCommonAction() 0 8 1
A addAction() 0 17 4
A getStep() 0 15 4
C getAction() 0 25 7
A getGlobalActions() 0 4 1
A getSteps() 0 4 1
A getInitialActions() 0 4 1
A getCommonActions() 0 4 1
A getCommonAction() 0 8 2
A getInitialAction() 17 17 4
A getJoins() 0 4 1
A getJoin() 17 17 4
A getMetaAttributes() 0 4 1
A getRegisters() 0 4 1
A getSplits() 0 4 1
A getSplit() 17 17 4
A setTriggerFunction() 0 11 2
A getTriggerFunction() 0 17 3
A getTriggerFunctions() 0 4 1
A addGlobalAction() 0 5 1
A addInitialAction() 0 5 1
A addJoin() 0 11 2
A addSplit() 0 11 2
A addStep() 0 11 2
A removeActionActionById() 0 6 1
B removeAction() 0 27 5
F writeXml() 11 137 18

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
/**
3
 * @link    https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow\Loader;
7
8
use OldTown\Workflow\Exception\ArgumentNotNumericException;
9
use OldTown\Workflow\Exception\InternalWorkflowException;
10
use OldTown\Workflow\Exception\InvalidArgumentException;
11
use OldTown\Workflow\Exception\InvalidDescriptorException;
12
use OldTown\Workflow\Exception\InvalidDtdSchemaException;
13
use OldTown\Workflow\Exception\InvalidWorkflowDescriptorException;
14
use DOMElement;
15
use OldTown\Workflow\Exception\InvalidWriteWorkflowException;
16
use OldTown\Workflow\Exception\RuntimeException;
17
use SplObjectStorage;
18
use DOMDocument;
19
use DOMImplementation;
20
use \LibXMLError;
21
22
/**
23
 * Interface WorkflowDescriptor
24
 *
25
 * @package OldTown\Workflow\Loader
26
 */
27
class WorkflowDescriptor extends AbstractDescriptor implements WriteXmlInterface
28
{
29
    /**
30
     *
31
     *
32
     * @var string
33
     */
34
    const DOCUMENT_TYPE_QUALIFIED_NAME = 'workflow';
35
36
    /**
37
     *
38
     *
39
     * @var string
40
     */
41
    const DOCUMENT_TYPE_PUBLIC_ID = '-//OpenSymphony Group//DTD OSWorkflow 2.8//EN';
42
43
    /**
44
     *
45
     *
46
     * @var string
47
     */
48
    const DOCUMENT_TYPE_SYSTEM_ID = 'http://www.opensymphony.com/osworkflow/workflow_2_8.dtd';
49
50
51
52
    /**
53
     * @var ConditionsDescriptor|null
54
     */
55
    protected $globalConditions;
56
57
    /**
58
     * @var ActionDescriptor[]|SplObjectStorage
59
     */
60
    protected $globalActions;
61
62
    /**
63
     * @var SplObjectStorage|ActionDescriptor[]
64
     */
65
    protected $initialActions;
66
67
    /**
68
     * @var JoinDescriptor[]|SplObjectStorage
69
     */
70
    protected $joins;
71
72
    /**
73
     * @var RegisterDescriptor[]|SplObjectStorage
74
     */
75
    protected $registers;
76
77
    /**
78
     * @var SplitDescriptor[]|SplObjectStorage
79
     */
80
    protected $splits = [];
81
82
    /**
83
     * @var StepDescriptor[]|SplObjectStorage
84
     */
85
    protected $steps;
86
87
    /**
88
     * @var ActionDescriptor[]
89
     */
90
    protected $commonActions = [];
91
92
    /**
93
     * @var ActionDescriptor[]|SplObjectStorage
94
     */
95
    protected $commonActionsList = [];
96
97
    /**
98
     * @var array
99
     */
100
    protected $metaAttributes = [];
101
102
    /**
103
     * @var array
104
     */
105
    protected $timerFunctions = [];
106
107
    /**
108
     * Имя workflow
109
     *
110
     * @var string|null
111
     */
112
    protected $workflowName;
113
114
    /**
115
     * @param DOMElement $element
116
     * @throws InvalidArgumentException
117
     * @throws RuntimeException
118
     * @throws InternalWorkflowException
119
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
120
     */
121 85
    public function __construct(DOMElement $element = null)
122
    {
123 85
        $this->registers = new SplObjectStorage();
124 85
        $this->initialActions = new SplObjectStorage();
125 85
        $this->globalActions = new SplObjectStorage();
126 85
        $this->steps = new SplObjectStorage();
127 85
        $this->joins = new SplObjectStorage();
128 85
        $this->splits = new SplObjectStorage();
129
130 85
        parent::__construct($element);
131
132 85
        if (null !== $element) {
133 68
            $this->init($element);
134 63
        }
135 80
    }
136
137
    /**
138
     * Возвращает имя workflow
139
     *
140
     * @return null|string
141
     */
142 1
    public function getName()
143
    {
144 1
        return $this->workflowName;
145
    }
146
147
148
    /**
149
     * Устанавливает имя workflow
150
     *
151
     * @param string $workflowName
152
     *
153
     * @return $this
154
     */
155 27
    public function setName($workflowName)
156
    {
157 27
        $this->workflowName = (string)$workflowName;
158
159 27
        return $this;
160
    }
161
162
    /**
163
     * Валидация workflow
164
     *
165
     * @throws InvalidWorkflowDescriptorException
166
     * @throws InternalWorkflowException
167
     * @throws InvalidDescriptorException
168
     * @throws InvalidWriteWorkflowException
169
     * @throws  \OldTown\Workflow\Exception\InvalidDtdSchemaException
170
     * @return void
171
     */
172 17
    public function validate()
173
    {
174 17
        $registers = $this->getRegisters();
175 17
        $triggerFunctions = $this->getTriggerFunctions();
176 17
        $globalActions = $this->getGlobalActions();
177 17
        $initialActions = $this->getInitialActions();
178 17
        $commonActions = $this->getCommonActions();
179 17
        $steps = $this->getSteps();
180 17
        $splits = $this->getSplits();
181 17
        $joins = $this->getJoins();
182
183
184 17
        ValidationHelper::validate($registers);
185 17
        ValidationHelper::validate($triggerFunctions);
186 17
        ValidationHelper::validate($globalActions);
187 17
        ValidationHelper::validate($initialActions);
188 17
        ValidationHelper::validate($commonActions);
189 17
        ValidationHelper::validate($steps);
190 17
        ValidationHelper::validate($splits);
191 17
        ValidationHelper::validate($joins);
192
193
194 17
        $actions = [];
195
196 17
        foreach ($globalActions as $action) {
197 3
            $actionId = $action->getId();
198 3
            if (array_key_exists($actionId, $actions)) {
199 1
                $errMsg = sprintf(
200 1
                    'Ошибка валидация. Действие с id %s уже существует',
201
                    $actionId
202 1
                );
203 1
                throw new InvalidWorkflowDescriptorException($errMsg);
204
            }
205 3
            $actions[$actionId] = $actionId;
206 17
        }
207
208 16
        foreach ($steps as $step) {
209 14
            $j = $step->getActions();
210
211 14
            foreach ($j as $action) {
212 14
                if (!$action->isCommon()) {
213 13
                    $actionId = $action->getId();
214 13
                    if (array_key_exists($actionId, $actions)) {
215 1
                        $errMsg = sprintf(
216 1
                            'Действие с id %s найденное у шага %s является дубликатом',
217 1
                            $actionId,
218 1
                            $step->getId()
219 1
                        );
220 1
                        throw new InvalidWorkflowDescriptorException($errMsg);
221
                    }
222 12
                    $actions[$actionId] = $actionId;
223 12
                }
224 13
            }
225 15
        }
226
227
228 15
        foreach ($commonActions as $action) {
229 2
            $actionId = $action->getId();
230 2
            if (array_key_exists($actionId, $actions)) {
231 1
                $errMsg = sprintf(
232 1
                    'common-action  с id %s является дубликатом',
233
                    $actionId
234 1
                );
235 1
                throw new InvalidWorkflowDescriptorException($errMsg);
236
            }
237 14
        }
238
239
240 14
        $this->validateDtd();
241 12
    }
242
243
    /**
244
     * Валидация схемы документа
245
     *
246
     * @return void
247
     * @throws InternalWorkflowException
248
     * @throws InvalidDescriptorException
249
     * @throws InvalidWriteWorkflowException
250
     * @throws  \OldTown\Workflow\Exception\InvalidDtdSchemaException
251
     */
252 14
    private function validateDtd()
253
    {
254 14
        $libxmlUseInternalErrors = libxml_use_internal_errors(true);
255 14
        $dom = $this->writeXml();
256
257 14
        $secureDtdEntityResolver = new SecureDtdEntityResolver();
258 14
        $dtd = $secureDtdEntityResolver->resolveEntity($dom->doctype);
259
260
261 14
        $systemId = 'data://text/plain;base64,'.base64_encode($dtd);
262
263 14
        $creator = new DOMImplementation;
264 14
        $doctype = $creator->createDocumentType(
265 14
            static::DOCUMENT_TYPE_QUALIFIED_NAME,
266 14
            static::DOCUMENT_TYPE_PUBLIC_ID,
267
            $systemId
268 14
        );
269 14
        $new = $creator->createDocument('', '', $doctype);
270
271 14
        $oldNode = $dom->getElementsByTagName(static::DOCUMENT_TYPE_QUALIFIED_NAME)->item(0);
272 14
        $newNode = $new->importNode($oldNode, true);
273 14
        $new->appendChild($newNode);
274
275 14
        if (!$new->validate()) {
276
            /** @var LibXMLError[] $errors */
277 2
            $errors = libxml_get_errors();
278 2
            $errMsgStack = [];
279 2
            foreach ($errors as $error) {
280 2
                $errMsgStack[] = $error->message;
281 2
            }
282 2
            $errMsg = implode(" \n", $errMsgStack);
283
284 2
            libxml_use_internal_errors($libxmlUseInternalErrors);
285 2
            throw new InvalidDtdSchemaException($errMsg);
286
        }
287 12
        libxml_use_internal_errors($libxmlUseInternalErrors);
288 12
    }
289
290
    /**
291
     * @param DOMElement $root
292
     * @throws InvalidArgumentException
293
     * @throws RuntimeException
294
     * @throws InternalWorkflowException
295
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
296
     */
297 68
    protected function init(DOMElement $root)
298
    {
299 68
        $metaElements = XmlUtil::getChildElements($root, 'meta');
300 68 View Code Duplication
        foreach ($metaElements as $meta) {
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...
301 12
            $value = XmlUtil::getText($meta);
302 12
            $name = XmlUtil::getRequiredAttributeValue($meta, 'name');
303
304 12
            $this->metaAttributes[$name] = $value;
305 68
        }
306
307
        // handle registers - OPTIONAL
308 68
        $r = XmlUtil::getChildElement($root, 'registers');
309 68 View Code Duplication
        if (null !== $r) {
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...
310 15
            $registers = XMLUtil::getChildElements($r, 'register');
311
312 15
            foreach ($registers as $register) {
313 15
                $registerDescriptor = DescriptorFactory::getFactory()->createRegisterDescriptor($register);
314 15
                $registerDescriptor->setParent($this);
315 15
                $this->registers->attach($registerDescriptor);
316 15
            }
317 15
        }
318
319
        // handle global-conditions - OPTIONAL
320 68
        $globalConditionsElement = XMLUtil::getChildElement($root, 'global-conditions');
321 68
        if ($globalConditionsElement !== null) {
322 1
            $globalConditions = XMLUtil::getChildElement($globalConditionsElement, 'conditions');
323
324 1
            $conditionsDescriptor = DescriptorFactory::getFactory()->createConditionsDescriptor($globalConditions);
325 1
            $conditionsDescriptor->setParent($this);
326 1
            $this->globalConditions = $conditionsDescriptor;
327 1
        }
328
329
        // handle initial-steps - REQUIRED
330 68
        $initialActionsElement = XMLUtil::getChildElement($root, 'initial-actions');
331 68
        $initialActions = XMLUtil::getChildElements($initialActionsElement, 'action');
0 ignored issues
show
Bug introduced by
It seems like $initialActionsElement defined by \OldTown\Workflow\Loader...oot, 'initial-actions') on line 330 can be null; however, OldTown\Workflow\Loader\...til::getChildElements() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
332
333 68
        foreach ($initialActions as $initialAction) {
334 49
            $actionDescriptor = DescriptorFactory::getFactory()->createActionDescriptor($initialAction);
335 49
            $actionDescriptor->setParent($this);
336 49
            $this->addInitialAction($actionDescriptor);
337 68
        }
338
339
        // handle global-actions - OPTIONAL
340 67
        $globalActionsElement = XMLUtil::getChildElement($root, 'global-actions');
341
342 67 View Code Duplication
        if (null !== $globalActionsElement) {
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...
343 7
            $globalActions = XMLUtil::getChildElements($globalActionsElement, 'action');
344
345 7
            foreach ($globalActions as $globalAction) {
346 7
                $actionDescriptor = DescriptorFactory::getFactory()->createActionDescriptor($globalAction);
347 7
                $actionDescriptor->setParent($this);
348 7
                $this->addGlobalAction($actionDescriptor);
349 7
            }
350 7
        }
351
352
353
        // handle common-actions - OPTIONAL
354
        //   - Store actions in HashMap for now. When parsing Steps, we'll resolve
355
        //      any common actions into local references.
356 67
        $commonActionsElement = XMLUtil::getChildElement($root, 'common-actions');
357
358 67 View Code Duplication
        if (null !== $commonActionsElement) {
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...
359 11
            $commonActions = XMLUtil::getChildElements($commonActionsElement, 'action');
360
361 11
            foreach ($commonActions as $commonAction) {
362 11
                $actionDescriptor = DescriptorFactory::getFactory()->createActionDescriptor($commonAction);
363 11
                $actionDescriptor->setParent($this);
364 11
                $this->addCommonAction($actionDescriptor);
365 11
            }
366 11
        }
367
368
369
        // handle timer-functions - OPTIONAL
370 67
        $timerFunctionsElement = XMLUtil::getChildElement($root, 'trigger-functions');
371
372 67
        if (null !== $timerFunctionsElement) {
373 3
            $timerFunctions = XMLUtil::getChildElements($timerFunctionsElement, 'trigger-function');
374
375 3
            foreach ($timerFunctions as $timerFunction) {
376 3
                $functionElement = XmlUtil::getRequiredChildElement($timerFunction, 'function');
377 3
                $id = XmlUtil::getRequiredAttributeValue($timerFunction, 'id');
378
379 3
                $function = DescriptorFactory::getFactory()->createFunctionDescriptor($functionElement);
380 3
                $function->setParent($this);
381
382 3
                $this->setTriggerFunction($id, $function);
383 2
            }
384 2
        }
385
386
        // handle steps - REQUIRED
387 66
        $stepsElement = XMLUtil::getChildElement($root, 'steps');
388 66
        $steps = XMLUtil::getChildElements($stepsElement, 'step');
0 ignored issues
show
Bug introduced by
It seems like $stepsElement defined by \OldTown\Workflow\Loader...Element($root, 'steps') on line 387 can be null; however, OldTown\Workflow\Loader\...til::getChildElements() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
389
390 66
        foreach ($steps as $step) {
391 64
            $stepDescriptor = DescriptorFactory::getFactory()->createStepDescriptor($step, $this);
392 64
            $this->addStep($stepDescriptor);
393 66
        }
394
395
396
        // handle splits - OPTIONAL:
397 65
        $splitsElement = XMLUtil::getChildElement($root, 'splits');
398 65 View Code Duplication
        if (null !== $splitsElement) {
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...
399 14
            $split = XMLUtil::getChildElements($splitsElement, 'split');
400 14
            foreach ($split as $s) {
401 14
                $splitDescriptor = DescriptorFactory::getFactory()->createSplitDescriptor($s);
402 14
                $splitDescriptor->setParent($this);
403 14
                $this->addSplit($splitDescriptor);
404 14
            }
405 13
        }
406
407
408
        // handle joins - OPTIONAL:
409 64
        $joinsElement = XMLUtil::getChildElement($root, 'joins');
410 64 View Code Duplication
        if (null !== $joinsElement) {
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...
411 14
            $join = XMLUtil::getChildElements($joinsElement, 'join');
412 14
            foreach ($join as $s) {
413 14
                $joinDescriptor = DescriptorFactory::getFactory()->createJoinDescriptor($s);
414 14
                $joinDescriptor->setParent($this);
415 14
                $this->addJoin($joinDescriptor);
416 14
            }
417 13
        }
418 63
    }
419
420
    /**
421
     * @return null|ConditionsDescriptor
422
     */
423 25
    public function getGlobalConditions()
424
    {
425 25
        return $this->globalConditions;
426
    }
427
428
429
    /**
430
     * Добавляет новый переход между действиями
431
     *
432
     * @param ActionDescriptor $descriptor
433
     * @return $this
434
     *
435
     * @throws InvalidArgumentException
436
     * @throws RuntimeException
437
     */
438 11
    public function addCommonAction(ActionDescriptor $descriptor)
439
    {
440 11
        $descriptor->setCommon(true);
441 11
        $this->addAction($this->commonActions, $descriptor);
442 11
        $this->addAction($this->commonActionsList, $descriptor);
443
444 11
        return $this;
445
    }
446
447
    /**
448
     * @param                  $actionsCollectionOrMap
449
     * @param ActionDescriptor $descriptor
450
     *
451
     * @return $this
452
     * @throws InvalidArgumentException
453
     * @throws RuntimeException
454
     */
455 53
    private function addAction(&$actionsCollectionOrMap, ActionDescriptor $descriptor)
456
    {
457 53
        $descriptorId = $descriptor->getId();
458 53
        $action = $this->getAction($descriptorId);
459 53
        if (null !== $action) {
460 1
            $errMsg = sprintf('Действие с id %s уже существует', $descriptorId);
461 1
            throw new InvalidArgumentException($errMsg);
462
        }
463
464 53
        if ($actionsCollectionOrMap instanceof SplObjectStorage) {
465 49
            $actionsCollectionOrMap->attach($descriptor);
466 53
        } elseif (is_array($actionsCollectionOrMap)) {
467 11
            $actionsCollectionOrMap[$descriptorId] = $descriptor;
468 11
        }
469
470 53
        return $this;
471
    }
472
473
    /**
474
     * Возвращает шаг по его id
475
     *
476
     * @param integer $id
477
     * @return StepDescriptor|null
478
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
479
     */
480 65
    public function getStep($id)
481
    {
482 65
        if (!is_numeric($id)) {
483 1
            $errMsg = 'Аргумент должен быть числом';
484 1
            throw new ArgumentNotNumericException($errMsg);
485
        }
486 64
        $id = (integer)$id;
487
488 64
        foreach ($this->getSteps() as $step) {
489 35
            if ($id === $step->getId()) {
490 21
                return $step;
491
            }
492 64
        }
493 64
        return null;
494
    }
495
496
    /**
497
     * @param integer $id
498
     * @return ActionDescriptor|null
499
     */
500 53
    public function getAction($id)
501
    {
502 53
        $id = (integer)$id;
503
504 53
        foreach ($this->getGlobalActions() as $actionDescriptor) {
505 7
            if ($id === $actionDescriptor->getId()) {
506 3
                return $actionDescriptor;
507
            }
508 53
        }
509
510 53
        foreach ($this->getSteps() as $stepDescriptor) {
511 3
            $actionDescriptor = $stepDescriptor->getAction($id);
512 3
            if (null !== $actionDescriptor) {
513 3
                return $actionDescriptor;
514
            }
515 53
        }
516
517 53
        foreach ($this->getInitialActions() as $actionDescriptor) {
518 9
            if ($id === $actionDescriptor->getId()) {
519 1
                return $actionDescriptor;
520
            }
521 53
        }
522
523 53
        return null;
524
    }
525
526
    /**
527
     * @return ActionDescriptor[]|SplObjectStorage
528
     */
529 56
    public function getGlobalActions()
530
    {
531 56
        return $this->globalActions;
532
    }
533
534
    /**
535
     * @return StepDescriptor[]|SplObjectStorage
536
     */
537 70
    public function getSteps()
538
    {
539 70
        return $this->steps;
540
    }
541
542
    /**
543
     * @return ActionDescriptor[]|SplObjectStorage
544
     */
545 57
    public function getInitialActions()
546
    {
547 57
        return $this->initialActions;
548
    }
549
550
    /**
551
     * @return ActionDescriptor[]
552
     */
553 21
    public function getCommonActions()
554
    {
555 21
        return $this->commonActions;
556
    }
557
558
    /**
559
     * @param $id
560
     *
561
     * @return ActionDescriptor|null
562
     */
563 14
    public function getCommonAction($id)
564
    {
565 14
        $id = (integer)$id;
566 14
        if (array_key_exists($id, $this->commonActions)) {
567 7
            return $this->commonActions[$id];
568
        }
569 8
        return null;
570
    }
571
572
573
    /**
574
     * @param $id
575
     *
576
     * @return ActionDescriptor|null
577
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
578
     */
579 22 View Code Duplication
    public function getInitialAction($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...
580
    {
581 22
        if (!is_numeric($id)) {
582 1
            $errMsg = 'Аргумент должен быть числом';
583 1
            throw new ArgumentNotNumericException($errMsg);
584
        }
585 21
        $id = (integer)$id;
586
587 21
        $initialActions = $this->getInitialActions();
588 21
        foreach ($initialActions as $actionDescriptor) {
589 20
            if ($id === $actionDescriptor->getId()) {
590 20
                return $actionDescriptor;
591
            }
592 6
        }
593
594 6
        return null;
595
    }
596
597
    /**
598
     * @return JoinDescriptor[]|SplObjectStorage
599
     */
600 22
    public function getJoins()
601
    {
602 22
        return $this->joins;
603
    }
604
605
606
    /**
607
     * @param $id
608
     *
609
     * @return JoinDescriptor|null
610
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
611
     */
612 15 View Code Duplication
    public function getJoin($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...
613
    {
614 15
        if (!is_numeric($id)) {
615 1
            $errMsg = 'Аргумент должен быть числом';
616 1
            throw new ArgumentNotNumericException($errMsg);
617
        }
618 14
        $id = (integer)$id;
619
620 14
        $joins = $this->getJoins();
621 14
        foreach ($joins as $joinDescriptor) {
622 2
            if ($id === $joinDescriptor->getId()) {
623 2
                return $joinDescriptor;
624
            }
625 14
        }
626
627 14
        return null;
628
    }
629
630
    /**
631
     * @return array
632
     */
633 17
    public function getMetaAttributes()
634
    {
635 17
        return $this->metaAttributes;
636
    }
637
638
    /**
639
     * @return RegisterDescriptor[]|SplObjectStorage
640
     */
641 39
    public function getRegisters()
642
    {
643 39
        return $this->registers;
644
    }
645
646
    /**
647
     * @return SplitDescriptor[]|SplObjectStorage
648
     */
649 22
    public function getSplits()
650
    {
651 22
        return $this->splits;
652
    }
653
654
655
656
    /**
657
     * @param $id
658
     *
659
     * @return SplitDescriptor|null
660
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
661
     */
662 15 View Code Duplication
    public function getSplit($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...
663
    {
664 15
        if (!is_numeric($id)) {
665 1
            $errMsg = 'Аргумент должен быть числом';
666 1
            throw new ArgumentNotNumericException($errMsg);
667
        }
668 14
        $id = (integer)$id;
669
670 14
        $splits = $this->getSplits();
671 14
        foreach ($splits as $splitDescriptor) {
672 13
            if ($id === $splitDescriptor->getId()) {
673 2
                return $splitDescriptor;
674
            }
675 14
        }
676
677 14
        return null;
678
    }
679
680
    /**
681
     * @param integer  $id
682
     * @param FunctionDescriptor $descriptor
683
     * @return $this
684
     *
685
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
686
     */
687 3
    public function setTriggerFunction($id, FunctionDescriptor $descriptor)
688
    {
689 3
        if (!is_numeric($id)) {
690 1
            $errMsg = 'Аргумент должен быть числом';
691 1
            throw new ArgumentNotNumericException($errMsg);
692
        }
693 2
        $id = (integer)$id;
694 2
        $this->timerFunctions[$id] = $descriptor;
695
696 2
        return $this;
697
    }
698
699
    /**
700
     * @param integer  $id
701
     * @return FunctionDescriptor
702
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
703
     */
704 3
    public function getTriggerFunction($id)
705
    {
706 3
        if (!is_numeric($id)) {
707 1
            $errMsg = 'Аргумент должен быть числом';
708 1
            throw new ArgumentNotNumericException($errMsg);
709
        }
710 2
        $id = (integer)$id;
711
712 2
        if (!array_key_exists($id, $this->timerFunctions)) {
713 1
            $errMsg = sprintf('Не найдена trigger-function с id %s', $id);
714 1
            throw new ArgumentNotNumericException($errMsg);
715
        }
716
717 1
        $this->timerFunctions[$id];
718
719 1
        return $this->timerFunctions[$id];
720
    }
721
722
    /**
723
     * @return FunctionDescriptor[]
724
     */
725 20
    public function getTriggerFunctions()
726
    {
727 20
        return $this->timerFunctions;
728
    }
729
730
    /**
731
     * @param ActionDescriptor $descriptor
732
     * @return $this
733
     * @throws InvalidArgumentException
734
     * @throws RuntimeException
735
     */
736 7
    public function addGlobalAction(ActionDescriptor $descriptor)
737
    {
738 7
        $this->addAction($this->globalActions, $descriptor);
739 7
        return $this;
740
    }
741
742
    /**
743
     * @param ActionDescriptor $descriptor
744
     * @return $this
745
     * @throws InvalidArgumentException
746
     * @throws RuntimeException
747
     */
748 49
    public function addInitialAction(ActionDescriptor $descriptor)
749
    {
750 49
        $this->addAction($this->initialActions, $descriptor);
751 49
        return $this;
752
    }
753
754
755
    /**
756
     * @param JoinDescriptor $descriptor
757
     * @return $this
758
     * @throws InvalidArgumentException
759
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
760
     */
761 14
    public function addJoin(JoinDescriptor $descriptor)
762
    {
763 14
        $id = $descriptor->getId();
764 14
        if (null !== $this->getJoin($id)) {
765 1
            $errMsg = sprintf('Объеденение с id %s уже существует', $id);
766 1
            throw new InvalidArgumentException($errMsg);
767
        }
768
769 14
        $this->getJoins()->attach($descriptor);
770 14
        return $this;
771
    }
772
773
774
    /**
775
     * @param SplitDescriptor $descriptor
776
     * @return $this
777
     * @throws InvalidArgumentException
778
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
779
     */
780 14
    public function addSplit(SplitDescriptor $descriptor)
781
    {
782 14
        $id = $descriptor->getId();
783 14
        if (null !== $this->getSplit($id)) {
784 1
            $errMsg = sprintf('Ветвление с id %s уже существует', $id);
785 1
            throw new InvalidArgumentException($errMsg);
786
        }
787
788 14
        $this->getSplits()->attach($descriptor);
789 14
        return $this;
790
    }
791
792
    /**
793
     * @param StepDescriptor $descriptor
794
     * @return $this
795
     *
796
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
797
     * @throws ArgumentNotNumericException
798
     */
799 64
    public function addStep(StepDescriptor $descriptor)
800
    {
801 64
        $id = $descriptor->getId();
802 64
        if (null !== $this->getStep($id)) {
803 1
            $errMsg = sprintf('Шаг с id %s уже существует', $id);
804 1
            throw new InvalidArgumentException($errMsg);
805
        }
806
807 64
        $this->getSteps()->attach($descriptor);
808 64
        return $this;
809
    }
810
811
    /**
812
     * Удаление action по id
813
     *
814
     * @param $id
815
     *
816
     * @return bool
817
     */
818 2
    public function removeActionActionById($id)
819
    {
820 2
        $descriptor = $this->getAction($id);
821
822 2
        return $this->removeAction($descriptor);
0 ignored issues
show
Bug introduced by
It seems like $descriptor defined by $this->getAction($id) on line 820 can be null; however, OldTown\Workflow\Loader\...criptor::removeAction() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
823
    }
824
825
    /**
826
     * @param ActionDescriptor $actionToRemove
827
     * @return boolean
828
     */
829 2
    public function removeAction(ActionDescriptor $actionToRemove)
830
    {
831 2
        $resultRemove = false;
832
833 2
        $actionToRemoveId = $actionToRemove->getId();
834 2
        $globalActions = $this->getGlobalActions();
835 2
        foreach ($globalActions as $actionDescriptor) {
836 2
            if ($actionToRemoveId === $actionDescriptor->getId()) {
837 1
                $globalActions->detach($actionDescriptor);
838
839 1
                $resultRemove = true;
840 1
            }
841 2
        }
842
843 2
        $steps = $this->getSteps();
844 2
        foreach ($steps as $stepDescriptor) {
845 2
            $actionDescriptor = $stepDescriptor->getAction($actionToRemoveId);
846
847 2
            if (null !== $actionDescriptor) {
848 1
                $stepDescriptor->getActions()->detach($actionDescriptor);
849
850 1
                $resultRemove = true;
851 1
            }
852 2
        }
853
854 2
        return $resultRemove;
855
    }
856
857
858
    /**
859
     * Создает DOMElement - эквивалентный состоянию дескриптора
860
     *
861
     * @param DOMDocument|null $dom
862
     *
863
     * @return DOMDocument
864
     * @throws InternalWorkflowException
865
     * @throws InvalidDescriptorException
866
     * @throws InvalidWriteWorkflowException
867
     *
868
     */
869 17
    public function writeXml(DOMDocument $dom = null)
870
    {
871 17
        if (null === $dom) {
872 15
            $imp = new DOMImplementation();
873 15
            $dtd  = $imp->createDocumentType(
874 15
                static::DOCUMENT_TYPE_QUALIFIED_NAME,
875 15
                static::DOCUMENT_TYPE_PUBLIC_ID,
876
                static::DOCUMENT_TYPE_SYSTEM_ID
877 15
            );
878 15
            $dom = $imp->createDocument('', '', $dtd);
879 15
            $dom->encoding = 'UTF-8';
880 15
            $dom->xmlVersion = '1.0';
881 15
            $dom->formatOutput = true;
882 15
        }
883
884 17
        $descriptor = $dom->createElement('workflow');
885
886 17
        $metaAttributes = $this->getMetaAttributes();
887 17
        $metaElementBase = $dom->createElement('meta');
888 17 View Code Duplication
        foreach ($metaAttributes as $metaAttributeName => $metaAttributeValue) {
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...
889 12
            $metaAttributeNameEncode = XmlUtil::encode($metaAttributeName);
890 12
            $metaAttributeValueEnEncode = XmlUtil::encode($metaAttributeValue);
891
892 12
            $metaElement = clone $metaElementBase;
893 12
            $metaElement->setAttribute('name', $metaAttributeNameEncode);
894 12
            $metaValueElement = $dom->createTextNode($metaAttributeValueEnEncode);
895 12
            $metaElement->appendChild($metaValueElement);
896
897 12
            $descriptor->appendChild($metaElement);
898 17
        }
899
900 17
        $registers = $this->getRegisters();
901 17
        if ($registers->count() > 0) {
902 12
            $registersElement = $dom->createElement('registers');
903 12
            foreach ($registers as $register) {
904 12
                $registerElement = $register->writeXml($dom);
905 12
                $registersElement->appendChild($registerElement);
906 12
            }
907
908 12
            $descriptor->appendChild($registersElement);
909 12
        }
910
911
912 17
        $timerFunctions = $this->getTriggerFunctions();
913 17
        if (count($timerFunctions) > 0) {
914 1
            $timerFunctionsElement = $dom->createElement('trigger-functions');
915 1
            $timerFunctionElementBase = $dom->createElement('trigger-function');
916 1
            foreach ($timerFunctions as $timerFunctionId => $timerFunction) {
917 1
                $timerFunctionElement = clone $timerFunctionElementBase;
918 1
                $timerFunctionElement->setAttribute('id', $timerFunctionId);
919
920 1
                $functionElement = $timerFunction->writeXml($dom);
921
922 1
                $timerFunctionElement->appendChild($functionElement);
923
924 1
                $timerFunctionsElement->appendChild($timerFunctionElement);
925 1
            }
926 1
            $descriptor->appendChild($timerFunctionsElement);
927 1
        }
928
929
930 17
        $globalConditions = $this->getGlobalConditions();
931 17
        if (null !== $globalConditions) {
932 1
            $globalConditionsElement = $dom->createElement('global-conditions');
933 1
            $globalConditionElement = $globalConditions->writeXml($dom);
934 1
            $globalConditionsElement->appendChild($globalConditionElement);
935 1
            $descriptor->appendChild($globalConditionsElement);
936 1
        }
937
938
939 17
        $initialActionsElement = $dom->createElement('initial-actions');
940 17
        $initialActions = $this->getInitialActions();
941 17
        foreach ($initialActions as $initialAction) {
942 13
            $initialActionElement = $initialAction->writeXml($dom);
943 13
            $initialActionsElement->appendChild($initialActionElement);
944 17
        }
945 17
        $descriptor->appendChild($initialActionsElement);
946
947
948 17
        $globalActions = $this->getGlobalActions();
949 17
        if ($globalActions->count() > 0) {
950 1
            $globalActionsElement = $dom->createElement('global-actions');
951 1
            foreach ($globalActions as $globalAction) {
952 1
                $globalActionElement = $globalAction->writeXml($dom);
953
954 1
                $globalActionsElement->appendChild($globalActionElement);
955 1
            }
956
957 1
            $descriptor->appendChild($globalActionsElement);
958 1
        }
959
960 17
        $commonActions = $this->getCommonActions();
961 17
        if (count($commonActions) > 0) {
962 3
            $commonActionsElement = $dom->createElement('common-actions');
963 3
            foreach ($commonActions as $commonAction) {
964 3
                $commonActionElement = $commonAction->writeXml($dom);
965 3
                $commonActionsElement->appendChild($commonActionElement);
966 3
            }
967
968 3
            $descriptor->appendChild($commonActionsElement);
969 3
        }
970
971
972 17
        $stepsElement = $dom->createElement('steps');
973 17
        $steps = $this->getSteps();
974 17
        foreach ($steps as $step) {
975 14
            $stepElement = $step->writeXml($dom);
976 14
            $stepsElement->appendChild($stepElement);
977 17
        }
978
979 17
        $descriptor->appendChild($stepsElement);
980
981 17
        $splits = $this->getSplits();
982 17
        if ($splits->count() > 0) {
983 12
            $splitsElement = $dom->createElement('splits');
984 12
            foreach ($splits as $split) {
985 12
                $splitElement = $split->writeXml($dom);
986 12
                $splitsElement->appendChild($splitElement);
987 12
            }
988
989 12
            $descriptor->appendChild($splitsElement);
990 12
        }
991
992 17
        $joins = $this->getJoins();
993 17
        if ($joins->count() > 0) {
994 12
            $joinsElement = $dom->createElement('joins');
995 12
            foreach ($joins as $join) {
996 12
                $joinElement = $join->writeXml($dom);
997 12
                $joinsElement->appendChild($joinElement);
998 12
            }
999
1000 12
            $descriptor->appendChild($joinsElement);
1001 12
        }
1002
1003 17
        $dom->appendChild($descriptor);
1004 17
        return $dom;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $dom; (DOMDocument) is incompatible with the return type declared by the interface OldTown\Workflow\Loader\...eXmlInterface::writeXml of type DOMElement.

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...
1005
    }
1006
}
1007