Completed
Push — master ( d67b97...75f235 )
by Jared
01:30
created

Iterator::getQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @see http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
12
namespace Pulsar;
13
14
final class Iterator implements \Iterator, \Countable, \ArrayAccess
15
{
16
    /**
17
     * @var Query
18
     */
19
    private $query;
20
21
    /**
22
     * @var int
23
     */
24
    private $start;
25
26
    /**
27
     * @var int
28
     */
29
    private $pointer;
30
31
    /**
32
     * @var int
33
     */
34
    private $limit;
35
36
    /**
37
     * @var int|bool
38
     */
39
    private $loadedStart;
40
41
    /**
42
     * @var array
43
     */
44
    private $models;
45
46
    /**
47
     * @var int|bool
48
     */
49
    private $count;
50
51
    public function __construct(Query $query)
52
    {
53
        $this->query = $query;
54
        $this->models = [];
55
        $this->start = $query->getStart();
56
        $this->limit = $query->getLimit();
57
        $this->pointer = $this->start;
58
59
        $sort = $query->getSort();
60
        if (empty($sort)) {
61
            $model = $query->getModel();
62
            $idProperties = $model::getIDProperties();
63
            foreach ($idProperties as &$property) {
64
                $property .= ' asc';
65
            }
66
67
            $query->sort(implode(',', $idProperties));
68
        }
69
    }
70
71
    public function getQuery(): Query
72
    {
73
        return $this->query;
74
    }
75
76
    //////////////////////////
77
    // Iterator Interface
78
    //////////////////////////
79
80
    /**
81
     * Rewind the Iterator to the first element.
82
     */
83
    public function rewind()
84
    {
85
        $this->pointer = $this->start;
86
        $this->loadedStart = false;
87
        $this->models = [];
88
        $this->count = false;
89
    }
90
91
    /**
92
     * Returns the current element.
93
     *
94
     * @return mixed
95
     */
96
    public function current()
97
    {
98
        if ($this->pointer >= $this->count()) {
99
            return null;
100
        }
101
102
        $this->loadModels();
103
        $k = $this->pointer % $this->limit;
104
105
        if (isset($this->models[$k])) {
106
            return $this->models[$k];
107
        }
108
109
        return null;
110
    }
111
112
    /**
113
     * Return the key of the current element.
114
     *
115
     * @return int
116
     */
117
    public function key()
118
    {
119
        return $this->pointer;
120
    }
121
122
    /**
123
     * Move forward to the next element.
124
     */
125
    public function next()
126
    {
127
        ++$this->pointer;
128
    }
129
130
    /**
131
     * Checks if current position is valid.
132
     *
133
     * @return bool
134
     */
135
    public function valid()
136
    {
137
        return $this->pointer < $this->count();
138
    }
139
140
    //////////////////////////
141
    // Countable Interface
142
    //////////////////////////
143
144
    /**
145
     * Get total number of models matching query.
146
     *
147
     * @return int
148
     */
149
    public function count()
150
    {
151
        $this->updateCount();
152
153
        return $this->count;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->count; of type integer|boolean adds the type boolean to the return on line 153 which is incompatible with the return type declared by the interface Countable::count of type integer.
Loading history...
154
    }
155
156
    //////////////////////////
157
    // ArrayAccess Interface
158
    //////////////////////////
159
160
    public function offsetExists($offset)
161
    {
162
        return is_numeric($offset) && $offset < $this->count();
163
    }
164
165
    public function offsetGet($offset)
166
    {
167
        if (!$this->offsetExists($offset)) {
168
            throw new \OutOfBoundsException("$offset does not exist on this Iterator");
169
        }
170
171
        $this->pointer = $offset;
172
173
        return $this->current();
174
    }
175
176
    public function offsetSet($offset, $value)
177
    {
178
        // iterators are immutable
179
        throw new \Exception('Cannot perform set on immutable Iterator');
180
    }
181
182
    public function offsetUnset($offset)
183
    {
184
        // iterators are immutable
185
        throw new \Exception('Cannot perform unset on immutable Iterator');
186
    }
187
188
    //////////////////////////
189
    // Private Methods
190
    //////////////////////////
191
192
    /**
193
     * Load the next round of models.
194
     */
195
    private function loadModels()
196
    {
197
        $start = $this->rangeStart($this->pointer, $this->limit);
198
        if ($this->loadedStart !== $start) {
199
            $this->query->start($start);
200
201
            $this->models = $this->query->execute();
202
            $this->loadedStart = $start;
0 ignored issues
show
Documentation Bug introduced by
It seems like $start of type double is incompatible with the declared type integer|boolean of property $loadedStart.

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...
203
        }
204
    }
205
206
    /**
207
     * Updates the total count of models. For better performance
208
     * the count is only updated on edges, which is when new models
209
     * need to be loaded.
210
     */
211
    private function updateCount()
212
    {
213
        // The count only needs to be updated when the pointer is
214
        // on the edges
215
        if (0 != $this->pointer % $this->limit &&
216
            $this->pointer < $this->count) {
217
            return;
218
        }
219
220
        $newCount = $this->query->count();
221
222
        // It's possible when iterating over models that something
223
        // is modified or deleted that causes the model count
224
        // to decrease. If the count has decreased then we
225
        // shift the pointer to prevent overflow.
226
        // This calculation is based on the assumption that
227
        // the first N (count - count') models are deleted.
228
        if (0 != $this->count && $newCount < $this->count) {
229
            $this->pointer = max(0, $this->pointer - ($this->count - $newCount));
0 ignored issues
show
Documentation Bug introduced by
It seems like max(0, $this->pointer - ...is->count - $newCount)) can also be of type double. However, the property $pointer is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
230
        }
231
232
        // If the count has increased then the pointer is still
233
        // valid. Update the count to include the extra models.
234
        $this->count = $newCount;
235
    }
236
237
    /**
238
     * Generates the starting page given a pointer and limit.
239
     */
240
    private function rangeStart(int $pointer, int $limit)
241
    {
242
        return floor($pointer / $limit) * $limit;
243
    }
244
245
    /**
246
     * Cast Iterator to array.
247
     */
248
    public function toArray(): array
249
    {
250
        return iterator_to_array($this);
251
    }
252
}
253