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.

MemoryWorkflowStore   F
last analyzed

Complexity

Total Complexity 100

Size/Duplication

Total Lines 685
Duplicated Lines 23.94 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 100
c 3
b 0
f 0
lcom 1
cbo 12
dl 164
loc 685
ccs 285
cts 285
cp 1
rs 3.5325

19 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 4 1
A getPropertySet() 17 17 3
A createPropertySet() 0 4 1
A setEntryState() 0 6 1
B findEntry() 0 22 4
A createEntry() 0 8 1
A createCurrentStep() 0 15 2
A findCurrentSteps() 15 15 3
A markFinished() 0 18 3
A reset() 0 9 1
B moveToHistory() 0 26 6
A findHistorySteps() 0 8 2
A query() 0 12 3
A queryInternal() 0 10 2
F checkExpression() 132 197 43
D checkNestedExpression() 0 28 9
B compareDate() 0 19 5
B compareLong() 0 19 5
B compareText() 0 19 5

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 MemoryWorkflowStore 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 MemoryWorkflowStore, 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\Spi\Memory;
7
8
use OldTown\PropertySet\PropertySetInterface;
9
use OldTown\PropertySet\PropertySetManager;
10
use OldTown\Workflow\Exception\ArgumentNotNumericException;
11
use OldTown\Workflow\Exception\InvalidWorkflowEntryException;
12
use OldTown\Workflow\Exception\NotFoundWorkflowEntryException;
13
use OldTown\Workflow\Query\FieldExpression;
14
use OldTown\Workflow\Query\NestedExpression;
15
use OldTown\Workflow\Query\WorkflowExpressionQuery;
16
use OldTown\Workflow\Spi\SimpleStep;
17
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
18
use OldTown\Workflow\Spi\StepInterface;
19
use OldTown\Workflow\Spi\WorkflowEntryInterface;
20
use OldTown\Workflow\Exception\StoreException;
21
use DateTime;
22
use OldTown\Workflow\Spi\WorkflowStoreInterface;
23
use SplObjectStorage;
24
use OldTown\Workflow\Exception\InvalidArgumentException;
25
26
/**
27
 * Class MemoryWorkflowStore
28
 *
29
 * @package OldTown\Workflow\Spi\Memory
30
 */
31
class MemoryWorkflowStore implements WorkflowStoreInterface
32
{
33
    /**
34
     * @var SimpleWorkflowEntry[]
35
     */
36
    protected static $entryCache = [];
37
38
    /**
39
     * @var SplObjectStorage[]|SimpleStep[]
40
     */
41
    protected static $currentStepsCache = [];
42
43
    /**
44
     * @var SplObjectStorage[]|SimpleStep[]
45
     */
46
    protected static $historyStepsCache = [];
47
48
    /**
49
     * @var array
50
     */
51
    protected static $propertySetCache = [];
52
53
    /**
54
     * @var int
55
     */
56
    protected static $globalEntryId = 1;
57
58
    /**
59
     * @var int
60
     */
61
    protected static $globalStepId = 1;
62
63
    /**
64
     * Вызывается один раз, при инициализации хранилища
65
     *
66
     * @param array $props
67
     * @throws StoreException
68
     */
69 20
    public function init(array $props = [])
70
    {
71
        // TODO: Implement init() method.
72 20
    }
73
74
    /**
75
     * Возвращает PropertySet that связанный с данным экземпляром workflow
76
     * @param integer $entryId id workflow
77
     * @return PropertySetInterface
78
     * @throws StoreException
79
     * @throws \OldTown\PropertySet\Exception\RuntimeException
80
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
81
     */
82 22 View Code Duplication
    public function getPropertySet($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...
83
    {
84 22
        if (array_key_exists($entryId, static::$propertySetCache)) {
85 17
            return static::$propertySetCache[$entryId];
86
        }
87
88 21
        if (!is_numeric($entryId)) {
89 1
            $errMsg = sprintf('Аргумент должен быть числом. Актуальное значение %s', $entryId);
90 1
            throw new ArgumentNotNumericException($errMsg);
91
        }
92 20
        $entryId = (integer)$entryId;
93
94 20
        $ps = $this->createPropertySet();
95 20
        static::$propertySetCache[$entryId] = $ps;
96
97 20
        return static::$propertySetCache[$entryId];
98
    }
99
100
    /**
101
     * Создаем экземпляр PropertySet
102
     *
103
     * @return PropertySetInterface
104
     */
105 19
    protected function createPropertySet()
106
    {
107 19
        return PropertySetManager::getInstance('memory');
108
    }
109
110
    //~ Methods ////////////////////////////////////////////////////////////////
111
112
    /**
113
     * Устанавливает статус для сущности workflow с заданным id
114
     *
115
     * @param int $entryId
116
     * @param int $state
117
     *
118
     * @return $this
119
     * @throws StoreException
120
     * @throws NotFoundWorkflowEntryException
121
     * @throws ArgumentNotNumericException
122
     * @throws InvalidWorkflowEntryException
123
     */
124 17
    public function setEntryState($entryId, $state)
125
    {
126
        /** @var SimpleWorkflowEntry $theEntry */
127 17
        $theEntry = $this->findEntry($entryId);
128 17
        $theEntry->setState($state);
129 17
    }
130
131
    /**
132
     * Ищет сущность workflow с заданным id во внутреннем кеше
133
     *
134
     * @param int $entryId
135
     *
136
     * @return WorkflowEntryInterface
137
     * @throws NotFoundWorkflowEntryException
138
     * @throws ArgumentNotNumericException
139
     * @throws InvalidWorkflowEntryException
140
     */
141 20
    public function findEntry($entryId)
142
    {
143 20
        if (!is_numeric($entryId)) {
144 1
            $errMsg = sprintf('Аргумент должен быть числом. Актуальное значение %s', $entryId);
145 1
            throw new ArgumentNotNumericException($errMsg);
146
        }
147
148 19
        if (!array_key_exists($entryId, static::$entryCache)) {
149 1
            $errMsg = sprintf('Не найдена сущность workflow с id %s', $entryId);
150 1
            throw new NotFoundWorkflowEntryException($errMsg);
151
        }
152
153 18
        $entry = static::$entryCache[$entryId];
154
155 18
        if (!$entry instanceof WorkflowEntryInterface) {
156 1
            $errMsg = sprintf('Сущность workflow должна реализовывать интерфейс %s', WorkflowEntryInterface::class);
157 1
            throw new InvalidWorkflowEntryException($errMsg);
158
        }
159
160
161 17
        return $entry;
162
    }
163
164
    /**
165
     * Создает экземпляр workflow
166
     *
167
     * @param string $workflowName
168
     *
169
     * @return SimpleWorkflowEntry
170
     */
171 32
    public function createEntry($workflowName)
172
    {
173 32
        $id = static::$globalEntryId++;
174 32
        $entry = new SimpleWorkflowEntry($id, $workflowName, WorkflowEntryInterface::CREATED);
175 32
        static::$entryCache[$id] = $entry;
176
177 32
        return $entry;
178
    }
179
180
    /**
181
     * Создает новый шаг
182
     *
183
     * @param integer  $entryId
184
     * @param integer  $stepId
185
     * @param string   $owner
186
     * @param DateTime $startDate
187
     * @param DateTime $dueDate
188
     * @param string   $status
189
     * @param array    $previousIds
190
     *
191
     * @return SimpleStep
192
     */
193 20
    public function createCurrentStep($entryId, $stepId, $owner = null, DateTime $startDate, DateTime $dueDate = null, $status, array $previousIds = [])
194
    {
195 20
        $id = static::$globalStepId++;
196 20
        $step = new SimpleStep($id, $entryId, $stepId, 0, $owner, $startDate, $dueDate, null, $status, $previousIds, null);
197
198 20
        if (!array_key_exists($entryId, static::$currentStepsCache)) {
199 20
            $currentSteps = new SplObjectStorage();
200 20
            static::$currentStepsCache[$entryId] = $currentSteps;
201 20
        }
202
203
204 20
        static::$currentStepsCache[$entryId]->attach($step);
0 ignored issues
show
Bug introduced by
The method attach does only exist in SplObjectStorage, but not in OldTown\Workflow\Spi\SimpleStep.

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...
205
206 20
        return $step;
207
    }
208
209
    /**
210
     * Ищет текущий набор шагов для сущности workflow c заданным id
211
     *
212
     * @param Integer $entryId
213
     *
214
     * @return SimpleStep[]|SplObjectStorage
215
     * @throws ArgumentNotNumericException
216
     */
217 21 View Code Duplication
    public function findCurrentSteps($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...
218
    {
219 21
        if (!is_numeric($entryId)) {
220 1
            $errMsg = sprintf('Аргумент должен быть числом. Актуальное значение %s', $entryId);
221 1
            throw new ArgumentNotNumericException($errMsg);
222
        }
223 20
        $entryId = (integer)$entryId;
224
225 20
        if (!array_key_exists($entryId, static::$currentStepsCache)) {
226 3
            $currentSteps = new SplObjectStorage();
227 3
            static::$currentStepsCache[$entryId] = $currentSteps;
228 3
        }
229
230 20
        return static::$currentStepsCache[$entryId];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::$currentStepsCache[$entryId]; (SplObjectStorage|OldTown\Workflow\Spi\SimpleStep) is incompatible with the return type declared by the interface OldTown\Workflow\Spi\Wor...rface::findCurrentSteps of type OldTown\Workflow\Spi\StepInterface[].

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
231
    }
232
233
    /**
234
     * Пометить текущий шаг как выполненный
235
     *
236
     * @param StepInterface $step
237
     * @param integer       $actionId
238
     * @param DateTime      $finishDate
239
     * @param string        $status
240
     * @param string        $caller
241
     *
242
     * @return null|SimpleStep
243
     *
244
     * @throws ArgumentNotNumericException
245
     */
246 8
    public function markFinished(StepInterface $step, $actionId, DateTime $finishDate, $status, $caller)
247
    {
248 8
        $entryId = $step->getEntryId();
249 8
        $currentSteps = $this->findCurrentSteps($entryId);
250
251 8
        foreach ($currentSteps as $theStep) {
0 ignored issues
show
Bug introduced by
The expression $currentSteps of type object<SplObjectStorage>...orkflow\Spi\SimpleStep> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
252 7
            if ($theStep->getId() === $step->getId()) {
253 7
                $theStep->setStatus($status);
254 7
                $theStep->setActionId($actionId);
255 7
                $theStep->setFinishDate($finishDate);
256 7
                $theStep->setCaller($caller);
257
258 7
                return $theStep;
259
            }
260 1
        }
261
262 1
        return null;
263
    }
264
265
    /**
266
     * Сбрасывает внутренние кеш хранилища.
267
     */
268 34
    public static function reset()
269
    {
270 34
        static::$entryCache = [];
271 34
        static::$currentStepsCache = [];
272 34
        static::$historyStepsCache = [];
273 34
        static::$propertySetCache = [];
274 34
        static::$globalEntryId = 1;
275 34
        static::$globalStepId = 1;
276 34
    }
277
278
    /**
279
     * Перенос шага в историю
280
     *
281
     * @param StepInterface $step
282
     *
283
     * @return void
284
     *
285
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
286
     */
287 8
    public function moveToHistory(StepInterface $step)
288
    {
289 8
        $entryId = $step->getEntryId();
290 8
        $currentSteps = $this->findCurrentSteps($entryId);
291
292 8
        if (!array_key_exists($entryId, static::$historyStepsCache)) {
293 7
            $historySteps = new SplObjectStorage();
294 7
            static::$historyStepsCache[$entryId] = $historySteps;
295 7
        }
296
297 8
        foreach ($currentSteps as $currentStep) {
0 ignored issues
show
Bug introduced by
The expression $currentSteps of type object<SplObjectStorage>...orkflow\Spi\SimpleStep> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
298 7
            if ($step->getId() === $currentStep->getId()) {
299 7
                $currentSteps->detach($currentStep);
0 ignored issues
show
Bug introduced by
The method detach does only exist in SplObjectStorage, but not in OldTown\Workflow\Spi\SimpleStep.

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...
300 7
                foreach (static::$historyStepsCache[$entryId] as $historyStep) {
0 ignored issues
show
Bug introduced by
The expression static::$historyStepsCache[$entryId] of type object<SplObjectStorage>...orkflow\Spi\SimpleStep> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
301
                    /** @var StepInterface $historyStep */
302 1
                    if ($historyStep->getId() === $step->getId()) {
303 1
                        static::$historyStepsCache[$entryId]->detach($historyStep);
304 1
                    }
305 7
                }
306
307 7
                static::$historyStepsCache[$entryId]->attach($currentStep);
0 ignored issues
show
Bug introduced by
The method attach does only exist in SplObjectStorage, but not in OldTown\Workflow\Spi\SimpleStep.

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...
308
309 7
                break;
310
            }
311 8
        }
312 8
    }
313
314
    /**
315
     * Поиск по истории шагов
316
     *
317
     * @param $entryId
318
     *
319
     * @return SimpleStep[]|SplObjectStorage
320
     */
321 2
    public function findHistorySteps($entryId)
322
    {
323 2
        if (array_key_exists($entryId, static::$historyStepsCache)) {
324 1
            return static::$historyStepsCache[$entryId];
0 ignored issues
show
Bug Compatibility introduced by
The expression static::$historyStepsCache[$entryId]; of type SplObjectStorage|OldTown\Workflow\Spi\SimpleStep adds the type OldTown\Workflow\Spi\SimpleStep to the return on line 324 which is incompatible with the return type declared by the interface OldTown\Workflow\Spi\Wor...rface::findHistorySteps of type OldTown\Workflow\Spi\Ste...face[]|SplObjectStorage.
Loading history...
325
        }
326
327 1
        return new SplObjectStorage();
328
    }
329
330
    /**
331
     * Поиск в хранилище
332
     *
333
     * @param WorkflowExpressionQuery $query
334
     *
335
     * @return array
336
     *
337
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
338
     */
339 13
    public function query(WorkflowExpressionQuery $query)
340
    {
341 13
        $results = [];
342
343 13
        foreach (static::$entryCache as $entryId => $mapEntry) {
344 12
            if ($this->queryInternal($entryId, $query)) {
345 5
                $results[$entryId] = $entryId;
346 5
            }
347 7
        }
348
349 7
        return $results;
350
    }
351
352
    /**
353
     * Реализация поиска в харинилище
354
     *
355
     * @param integer                 $entryId
356
     * @param WorkflowExpressionQuery $query
357
     *
358
     * @return bool
359
     *
360
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
361
     */
362 12
    protected function queryInternal($entryId, WorkflowExpressionQuery $query)
363
    {
364 12
        $expression = $query->getExpression();
365
366 12
        if ($expression->isNested()) {
367 3
            return $this->checkNestedExpression($entryId, $expression);
0 ignored issues
show
Compatibility introduced by
$expression of type object<OldTown\Workflow\Query\AbstractExpression> is not a sub-type of object<OldTown\Workflow\Query\NestedExpression>. It seems like you assume a child class of the class OldTown\Workflow\Query\AbstractExpression to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
368
        } else {
369 9
            return $this->checkExpression($entryId, $expression);
0 ignored issues
show
Compatibility introduced by
$expression of type object<OldTown\Workflow\Query\AbstractExpression> is not a sub-type of object<OldTown\Workflow\Query\FieldExpression>. It seems like you assume a child class of the class OldTown\Workflow\Query\AbstractExpression to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
370
        }
371
    }
372
373
//
374
375
    /**
376
     * Проверка выражения
377
     *
378
     * @param integer         $entryId
379
     * @param FieldExpression $expression
380
     *
381
     * @return bool
382
     *
383
     * @throws InvalidArgumentException
384
     */
385 12
    private function checkExpression($entryId, FieldExpression $expression)
386
    {
387 12
        $value = $expression->getValue();
388 12
        $operator = $expression->getOperator();
389 12
        $field = $expression->getField();
390 12
        $context = $expression->getContext();
391
392 12
        $id = (integer)$entryId;
393
394 12
        if ($context === FieldExpression::ENTRY) {
395 9
            $theEntry = static::$entryCache[$id];
396
397 9
            if ($field === FieldExpression::NAME) {
398 3
                return $this->compareText($theEntry->getWorkflowName(), $value, $operator);
399
            }
400
401 7
            if ($field === FieldExpression::STATE) {
402 6
                if (!is_numeric($value)) {
403 1
                    $errMsg = 'unknown field';
404 1
                    throw new InvalidArgumentException($errMsg);
405
                }
406
407 5
                $valueInt = (integer)$value;
408
409 5
                return $this->compareLong($valueInt, $theEntry->getState(), $operator);
410
            }
411
412 1
            $errMsg = 'unknown field';
413 1
            throw new InvalidArgumentException($errMsg);
414
        }
415
416
        /** @var SplObjectStorage[]|SimpleStep[] $steps */
417 3
        $steps = [];
418
419 3
        if ($context === FieldExpression::CURRENT_STEPS) {
420 1
            $steps = array_key_exists($id, static::$currentStepsCache) ? static::$currentStepsCache[$id] : $steps;
421 3
        } elseif ($context === FieldExpression::HISTORY_STEPS) {
422 1
            $steps = array_key_exists($id, static::$historyStepsCache) ? static::$historyStepsCache[$id] : $steps;
423 1
        } else {
424 1
            $errMsg = 'unknown field context';
425 1
            throw new InvalidArgumentException($errMsg);
426
        }
427
428 2
        if (0 === count($steps)) {
429 2
            return false;
430
        }
431
432 2
        $expressionResult = false;
433
434
        switch ($field) {
435 2 View Code Duplication
            case FieldExpression::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...
436 1
                if (!is_numeric($value)) {
437 1
                    $errMsg = 'unknown field';
438 1
                    throw new InvalidArgumentException($errMsg);
439
                }
440
441 1
                $actionId = (integer)$value;
442
443 1
                foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
444 1
                    if ($this->compareLong($step->getActionId(), $actionId, $operator)) {
445 1
                        $expressionResult = true;
446
447 1
                        break;
448
                    }
449 1
                }
450
451
452 1
                break;
453
454 2 View Code Duplication
            case FieldExpression::CALLER:
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...
455 1
                $caller = $value;
456 1
                if ('string' !== gettype($caller)) {
457 1
                    $errMsg = 'unknown field';
458 1
                    throw new InvalidArgumentException($errMsg);
459
                }
460
461 1
                foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
462 1
                    if ($this->compareText($step->getCaller(), $caller, $operator)) {
463 1
                        $expressionResult = true;
464
465 1
                        break;
466
                    }
467 1
                }
468
469 1
                break;
470
471 2 View Code Duplication
            case FieldExpression::FINISH_DATE:
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...
472 1
                if ($value instanceof DateTime) {
473 1
                    $finishDate = $value;
474 1
                    foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
475 1
                        if ($this->compareDate($step->getFinishDate(), $finishDate, $operator)) {
476 1
                            $expressionResult = true;
477
478 1
                            break;
479
                        }
480 1
                    }
481 1
                } else {
482 1
                    $errMsg = 'unknown field';
483 1
                    throw new InvalidArgumentException($errMsg);
484
                }
485
486 1
                break;
487
488 1 View Code Duplication
            case FieldExpression::OWNER:
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...
489 1
                $owner = $value;
490 1
                if ('string' !== gettype($owner)) {
491 1
                    $errMsg = 'unknown field';
492 1
                    throw new InvalidArgumentException($errMsg);
493
                }
494
495 1
                foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
496 1
                    if ($this->compareText($step->getOwner(), $owner, $operator)) {
497 1
                        $expressionResult = true;
498
499 1
                        break;
500
                    }
501 1
                }
502
503
504 1
                break;
505
506 1 View Code Duplication
            case FieldExpression::START_DATE:
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...
507 1
                if ($value instanceof DateTime) {
508 1
                    $startDate = $value;
509 1
                    foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
510 1
                        if ($this->compareDate($step->getStartDate(), $startDate, $operator)) {
511 1
                            $expressionResult = true;
512
513 1
                            break;
514
                        }
515 1
                    }
516 1
                } else {
517 1
                    $errMsg = 'unknown field';
518 1
                    throw new InvalidArgumentException($errMsg);
519
                }
520
521 1
                break;
522
523 1 View Code Duplication
            case FieldExpression::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...
524 1
                if (!is_numeric($value)) {
525 1
                    $errMsg = 'unknown field';
526 1
                    throw new InvalidArgumentException($errMsg);
527
                }
528 1
                $stepId = (integer)$value;
529
530 1
                foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
531 1
                    if ($this->compareLong($step->getStepId(), $stepId, $operator)) {
532 1
                        $expressionResult = true;
533
534 1
                        break;
535
                    }
536 1
                }
537
538 1
                break;
539
540 1 View Code Duplication
            case FieldExpression::STATUS:
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...
541 1
                $status = $value;
542 1
                if ('string' !== gettype($status)) {
543 1
                    $errMsg = 'unknown field';
544 1
                    throw new InvalidArgumentException($errMsg);
545
                }
546
547 1
                foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
548 1
                    if ($this->compareText($step->getStatus(), $status, $operator)) {
549 1
                        $expressionResult = true;
550
551 1
                        break;
552
                    }
553 1
                }
554
555 1
                break;
556
557 1 View Code Duplication
            case FieldExpression::DUE_DATE:
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...
558 1
                if ($value instanceof DateTime) {
559 1
                    $dueDate = $value;
560 1
                    foreach ($steps as $step) {
0 ignored issues
show
Bug introduced by
The expression $steps of type object<SplObjectStorage>...rkflow\Spi\SimpleStep>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
561 1
                        if ($this->compareDate($step->getDueDate(), $dueDate, $operator)) {
562 1
                            $expressionResult = true;
563
564 1
                            break;
565
                        }
566 1
                    }
567 1
                } else {
568 1
                    $errMsg = 'unknown field';
569 1
                    throw new InvalidArgumentException($errMsg);
570
                }
571
572
573 1
                break;
574
        }
575
576 2
        if ($expression->isNegate()) {
577 1
            return !$expressionResult;
578
        } else {
579 2
            return $expressionResult;
580
        }
581
    }
582
583
    /**
584
     * Проверка вложенных выражений
585
     *
586
     * @param integer          $entryId
587
     * @param NestedExpression $nestedExpression
588
     *
589
     * @return bool
590
     * @throws InvalidArgumentException
591
     */
592 3
    private function checkNestedExpression($entryId, NestedExpression $nestedExpression)
593
    {
594 3
        $expressions = $nestedExpression->getExpressions();
595 3
        foreach ($expressions as $expression) {
596 3
            if ($expression->isNested()) {
597 1
                $expressionResult = $this->checkNestedExpression($entryId, $expression);
0 ignored issues
show
Compatibility introduced by
$expression of type object<OldTown\Workflow\Query\AbstractExpression> is not a sub-type of object<OldTown\Workflow\Query\NestedExpression>. It seems like you assume a child class of the class OldTown\Workflow\Query\AbstractExpression to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
598 1
            } else {
599 3
                $expressionResult = $this->checkExpression($entryId, $expression);
0 ignored issues
show
Compatibility introduced by
$expression of type object<OldTown\Workflow\Query\AbstractExpression> is not a sub-type of object<OldTown\Workflow\Query\FieldExpression>. It seems like you assume a child class of the class OldTown\Workflow\Query\AbstractExpression to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
600
            }
601
602 3
            if (false === $expressionResult && $nestedExpression->getExpressionOperator() === NestedExpression::AND_OPERATOR) {
603 1
                return $nestedExpression->isNegate();
604
            }
605 3
            if (true === $expressionResult && $nestedExpression->getExpressionOperator() === NestedExpression::OR_OPERATOR) {
606 1
                return !$nestedExpression->isNegate();
607
            }
608 3
        }
609
610
611 3
        if ($nestedExpression->getExpressionOperator() === NestedExpression::AND_OPERATOR) {
612 1
            return !$nestedExpression->isNegate();
613 2
        } elseif ($nestedExpression->getExpressionOperator() === NestedExpression::OR_OPERATOR) {
614 1
            return $nestedExpression->isNegate();
615
        }
616
617 1
        $errMsg = 'unknown field';
618 1
        throw new InvalidArgumentException($errMsg);
619
    }
620
621
    /**
622
     * Сравнение дат
623
     *
624
     * @param DateTime $value1
625
     * @param DateTime $value2
626
     * @param integer  $operator
627
     *
628
     * @return bool
629
     * @throws InvalidArgumentException
630
     */
631 2
    private function compareDate(DateTime $value1, DateTime $value2, $operator)
632
    {
633 2
        $diff = $value1->format('U') - $value2->format('U');
634
        switch ($operator) {
635 2
            case FieldExpression::EQUALS:
636 2
                return $diff === 0;
637 1
            case FieldExpression::NOT_EQUALS:
638 1
                return $diff !== 0;
639
640 1
            case FieldExpression::GT:
641 1
                return $diff > 0;
642
643 1
            case FieldExpression::LT:
644 1
                return $diff < 0;
645
        }
646
647 1
        $errMsg = 'unknown field operator';
648 1
        throw new InvalidArgumentException($errMsg);
649
    }
650
651
    /**
652
     * Сравнивает целые числа
653
     *
654
     * @param string  $value1
655
     * @param string  $value2
656
     * @param integer $operator
657
     *
658
     * @return bool
659
     *
660
     * @throws InvalidArgumentException
661
     */
662 6
    private function compareLong($value1, $value2, $operator)
663
    {
664
        switch ($operator) {
665 6
            case FieldExpression::EQUALS:
666 5
                return $value1 === $value2;
667
668 2
            case FieldExpression::NOT_EQUALS:
669 1
                return $value1 !== $value2;
670
671 2
            case FieldExpression::GT:
672 1
                return $value1 > $value2;
673
674 2
            case FieldExpression::LT:
675 1
                return $value1 < $value2;
676
        }
677
678 1
        $errMsg = 'unknown field operator';
679 1
        throw new InvalidArgumentException($errMsg);
680
    }
681
682
    /**
683
     *
684
     * @todo поправить для юникода
685
     *
686
     * Сравнение строк
687
     *
688
     * @param $value1
689
     * @param $value2
690
     * @param $operator
691
     *
692
     * @return bool
693
     *
694
     * @throws \OldTown\Workflow\Exception\InvalidArgumentException
695
     */
696 5
    private function compareText($value1, $value2, $operator)
697
    {
698
        switch ($operator) {
699 5
            case FieldExpression::EQUALS:
700 4
                return $value1 === $value2;
701
702 2
            case FieldExpression::NOT_EQUALS:
703 1
                return $value1 !== $value2;
704
705 2
            case FieldExpression::GT:
706 1
                return strlen($value1) > strlen($value2);
707
708 2
            case FieldExpression::LT:
709 1
                return strlen($value1) < strlen($value2);
710
        }
711
712 1
        $errMsg = 'unknown field operator';
713 1
        throw new InvalidArgumentException($errMsg);
714
    }
715
}
716