Passed
Push — fix-FRAM-86-reset-clause-on-ke... ( 4c1285...bb37ea )
by Vincent
24:06 queued 17:22
created

KeyWalkStrategy::next()   B

Complexity

Conditions 10
Paths 68

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10.0094

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 22
c 1
b 0
f 0
dl 0
loc 41
ccs 21
cts 22
cp 0.9545
rs 7.6666
cc 10
nc 68
nop 1
crap 10.0094

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Bdf\Prime\Query\Pagination\WalkStrategy;
4
5
use Bdf\Prime\Collection\CollectionInterface;
6
use Bdf\Prime\Connection\ConnectionInterface;
7
use Bdf\Prime\Query\Clause;
8
use Bdf\Prime\Query\CompilableClause;
9
use Bdf\Prime\Query\Contract\Limitable;
10
use Bdf\Prime\Query\Contract\Orderable;
11
use Bdf\Prime\Query\Contract\ReadOperation;
12
use Bdf\Prime\Query\Contract\Whereable;
13
use Bdf\Prime\Query\ReadCommandInterface;
14
use InvalidArgumentException;
15
16
/**
17
 * Walk strategy using a primary key (or any unique key) as cursor
18
 * This strategy supports deleting entities during the walk, but the entity must contains a single primary key, and the query must be ordered by this key
19
 * Any sort on other attribute are not supported
20
 *
21
 * @template E as object
22
 * @implements WalkStrategyInterface<E>
23
 */
24
final class KeyWalkStrategy implements WalkStrategyInterface
25
{
26
    /**
27
     * @var KeyInterface<E>
28
     */
29
    private $key;
30
31
    /**
32
     * PrimaryKeyWalkStrategy constructor.
33
     *
34
     * @param KeyInterface<E> $key
35
     */
36 19
    public function __construct(KeyInterface $key)
37
    {
38 19
        $this->key = $key;
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 16
    public function initialize(ReadCommandInterface $query, int $chunkSize, int $startPage): WalkCursor
45
    {
46 16
        if (!self::supports($query, $startPage, $this->key->name())) {
47 3
            throw new InvalidArgumentException('KeyWalkStrategy is not supported by this query');
48
        }
49
50
        /** @var Limitable&Orderable&ReadCommandInterface<ConnectionInterface, E> $query */
51 13
        $query = clone $query;
52
53 13
        if (!isset($query->getOrders()[$this->key->name()])) {
54 13
            $query->order($this->key->name(), Orderable::ORDER_ASC);
55
        }
56
57 13
        $query->limit($chunkSize);
58
59
        /** @var WalkCursor<E> */
60 13
        return new WalkCursor($query);
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    #[ReadOperation]
67 13
    public function next(WalkCursor $cursor): WalkCursor
68
    {
69 13
        $cursor = clone $cursor;
70
71 13
        if ($cursor->entities) {
72 11
            $cursor->cursor = $this->getLastKeyOfEntities($cursor);
73
        }
74
75 13
        if ($cursor->cursor !== null) {
76
            /** @var ReadCommandInterface<ConnectionInterface, E>&Orderable&Whereable $query */
77 11
            $query = $cursor->query;
78 11
            $column = $this->key->name();
79 11
            $operator = $query->getOrders()[$column] === Orderable::ORDER_ASC ? '>' : '<';
0 ignored issues
show
Bug introduced by
The method getOrders() does not exist on Bdf\Prime\Query\ReadCommandInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Bdf\Prime\Query\Contract...\KeyValueQueryInterface or Bdf\Prime\Query\QueryInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

79
            $operator = $query->/** @scrutinizer ignore-call */ getOrders()[$column] === Orderable::ORDER_ASC ? '>' : '<';
Loading history...
80
81
            // Quick fix for FRAM-86 : reset where clause
82 11
            $set = false;
83
84 11
            if ($query instanceof CompilableClause) {
85 11
                foreach ($query->statements['where'] as $key => $statement) {
86 10
                    if ($statement['column'] === $column && $statement['operator'] === $operator) {
87 10
                        $query->statements['where'][$key]['value'] = $cursor->cursor;
88 10
                        $set = true;
89 10
                        $query->state()->invalidate('where');
90 10
                        break;
91
                    }
92
                }
93
            }
94
95 11
            if (!$set) {
96 11
                $query->where($column, $operator, $cursor->cursor);
0 ignored issues
show
Bug introduced by
The method where() does not exist on Bdf\Prime\Query\ReadCommandInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdf\Prime\Query\ReadCommandInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
                $query->/** @scrutinizer ignore-call */ 
97
                        where($column, $operator, $cursor->cursor);
Loading history...
97
            }
98
        }
99
100 13
        $cursor->entities = $cursor->query->all();
0 ignored issues
show
Documentation Bug introduced by
It seems like $cursor->query->all() of type Bdf\Prime\Query\R[]&Bdf\...ion\CollectionInterface is incompatible with the declared type Bdf\Prime\Query\Pagination\WalkStrategy\R[]|null of property $entities.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
101
102 13
        if ($cursor->entities instanceof CollectionInterface) {
103
            $cursor->entities = $cursor->entities->all();
104
        }
105
106 13
        return $cursor;
107
    }
108
109
    /**
110
     * Check if the strategy supports the given parameters
111
     *
112
     * @param ReadCommandInterface $query The query
113
     * @param int|null $startPage The start page
114
     * @param string $key The cursor key
115
     *
116
     * @return bool
117
     *
118
     * @psalm-assert-if-true Orderable&Limitable&Whereable $query
119
     */
120 19
    public static function supports(ReadCommandInterface $query, ?int $startPage, string $key): bool
121
    {
122 19
        if ($startPage !== null && $startPage !== 1) {
123 3
            return false;
124
        }
125
126 17
        if (!($query instanceof Orderable && $query instanceof Limitable && $query instanceof Whereable)) {
127 2
            return false;
128
        }
129
130 16
        $orders = $query->getOrders();
131
132 16
        return empty($orders) || (count($orders) === 1 && isset($orders[$key]));
133
    }
134
135
    /**
136
     * Find the last key to be set as cursor value
137
     *
138
     * @param WalkCursor $cursor
139
     * @return mixed
140
     *
141
     * @see WalkCursor::$cursor
142
     */
143 11
    private function getLastKeyOfEntities(WalkCursor $cursor)
144
    {
145 11
        $lastEntity = end($cursor->entities);
0 ignored issues
show
Bug introduced by
It seems like $cursor->entities can also be of type null; however, parameter $array of end() does only seem to accept array|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
        $lastEntity = end(/** @scrutinizer ignore-type */ $cursor->entities);
Loading history...
146
147
        // Basic select query : results are an ordered list, so the last key is always the key of the last entity
148 11
        if (array_is_list($cursor->entities)) {
149 8
            return $this->key->get($lastEntity);
150
        }
151
152
        // group by query
153
        // Because index can be overridden (or value are added), order is not guaranteed
154
        // So we should iterate other entities to find the "max" key
155
        // In case of "by combine", values of each key are ordered list, so we simply need to take the last entity's key of each index
156 3
        $lastKey = $this->key->get(is_array($lastEntity) ? end($lastEntity) : $lastEntity);
157
158
        /** @var ReadCommandInterface<ConnectionInterface, E>&Orderable&Whereable $query */
159 3
        $query = $cursor->query;
160 3
        $asc = $query->getOrders()[$this->key->name()] === Orderable::ORDER_ASC;
161
162 3
        foreach ($cursor->entities as $entity) {
163 3
            $key = $this->key->get(is_array($entity) ? end($entity) : $entity);
164 3
            $gt = $key > $lastKey;
165
166
            // order is ascendant and key is > lastKey
167
            // or order is descendant and key is < lastKey
168 3
            if ($asc === $gt) {
169 2
                $lastKey = $key;
170
            }
171
        }
172
173 3
        return $lastKey;
174
    }
175
}
176