KeyWalkStrategy::next()   C
last analyzed

Complexity

Conditions 12
Paths 68

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 12.0104

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 25
c 1
b 0
f 0
dl 0
loc 45
ccs 23
cts 24
cp 0.9583
rs 6.9666
cc 12
nc 68
nop 1
crap 12.0104

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 && !empty($query->statements['where'])) {
85 10
                foreach ($query->statements['where'] as $key => $statement) {
86
                    if (
87 10
                        isset($statement['column'], $statement['operator'], $statement['value'])
88 10
                        && $statement['column'] === $column
89 10
                        && $statement['operator'] === $operator
90
                    ) {
91 10
                        $query->statements['where'][$key]['value'] = $cursor->cursor;
92 10
                        $set = true;
93 10
                        $query->state()->invalidate('where');
94 10
                        break;
95
                    }
96
                }
97
            }
98
99 11
            if (!$set) {
100 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

100
                $query->/** @scrutinizer ignore-call */ 
101
                        where($column, $operator, $cursor->cursor);
Loading history...
Bug introduced by
The method where() does not exist on Bdf\Prime\Query\CompilableClause. It seems like you code against a sub-type of Bdf\Prime\Query\CompilableClause such as Bdf\Prime\Query\AbstractReadCommand. ( Ignorable by Annotation )

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

100
                $query->/** @scrutinizer ignore-call */ 
101
                        where($column, $operator, $cursor->cursor);
Loading history...
101
            }
102
        }
103
104 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...
105
106 13
        if ($cursor->entities instanceof CollectionInterface) {
107
            $cursor->entities = $cursor->entities->all();
108
        }
109
110 13
        return $cursor;
111
    }
112
113
    /**
114
     * Check if the strategy supports the given parameters
115
     *
116
     * @param ReadCommandInterface $query The query
117
     * @param int|null $startPage The start page
118
     * @param string $key The cursor key
119
     *
120
     * @return bool
121
     *
122
     * @psalm-assert-if-true Orderable&Limitable&Whereable $query
123
     */
124 19
    public static function supports(ReadCommandInterface $query, ?int $startPage, string $key): bool
125
    {
126 19
        if ($startPage !== null && $startPage !== 1) {
127 3
            return false;
128
        }
129
130 17
        if (!($query instanceof Orderable && $query instanceof Limitable && $query instanceof Whereable)) {
131 2
            return false;
132
        }
133
134 16
        $orders = $query->getOrders();
135
136 16
        return empty($orders) || (count($orders) === 1 && isset($orders[$key]));
137
    }
138
139
    /**
140
     * Find the last key to be set as cursor value
141
     *
142
     * @param WalkCursor $cursor
143
     * @return mixed
144
     *
145
     * @see WalkCursor::$cursor
146
     */
147 11
    private function getLastKeyOfEntities(WalkCursor $cursor)
148
    {
149 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

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