Passed
Pull Request — master (#183)
by David
03:09
created

CachingIterator::offsetGet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
4
namespace TheCodingMachine\TDBM\Iterators;
5
6
7
use Countable;
8
use Iterator;
9
use TheCodingMachine\TDBM\AbstractTDBMObject;
10
use TheCodingMachine\TDBM\InnerResultIteratorInterface;
11
use TheCodingMachine\TDBM\TDBMInvalidOffsetException;
12
use TheCodingMachine\TDBM\TDBMInvalidOperationException;
13
use Traversable;
14
use function current;
15
use function filter_var;
16
use function get_class;
17
use function key;
18
use function next;
19
use const FILTER_VALIDATE_INT;
20
21
/**
22
 * An iterator that caches results (just like \CachingIterator), but that also accepts seeking a value even if
23
 * iteration did not started yet.
24
 */
25
class CachingIterator implements Iterator, InnerResultIteratorInterface
26
{
27
    /**
28
     * The list of results already fetched.
29
     *
30
     * @var AbstractTDBMObject[]
31
     */
32
    private $results = [];
33
    /**
34
     * @var Traversable
35
     */
36
    private $iterator;
37
    /**
38
     * @var bool
39
     */
40
    private $fetchStarted = false;
41
    /**
42
     * @var mixed
43
     */
44
    private $current;
45
    /**
46
     * @var int
47
     */
48
    private $key;
49
50
    public function __construct(Traversable $iterator)
51
    {
52
        $this->iterator = $iterator;
53
    }
54
55
    /**
56
     * Whether a offset exists.
57
     *
58
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
59
     *
60
     * @param mixed $offset <p>
61
     *                      An offset to check for.
62
     *                      </p>
63
     *
64
     * @return bool true on success or false on failure.
65
     *              </p>
66
     *              <p>
67
     *              The return value will be casted to boolean if non-boolean was returned
68
     *
69
     * @since 5.0.0
70
     */
71
    public function offsetExists($offset)
72
    {
73
        try {
74
            $this->toIndex($offset);
75
        } catch (TDBMInvalidOffsetException $e) {
76
            return false;
77
        }
78
79
        return true;
80
    }
81
82
    /**
83
     * Offset to retrieve.
84
     *
85
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
86
     *
87
     * @param mixed $offset <p>
88
     *                      The offset to retrieve.
89
     *                      </p>
90
     *
91
     * @return mixed Can return all value types
92
     *
93
     * @since 5.0.0
94
     */
95
    public function offsetGet($offset)
96
    {
97
        $this->toIndex($offset);
98
99
        return $this->results[$offset];
100
    }
101
102
    /**
103
     * @param mixed $offset
104
     * @throws TDBMInvalidOffsetException
105
     */
106
    private function toIndex($offset): void
107
    {
108
        if ($offset < 0 || filter_var($offset, FILTER_VALIDATE_INT) === false) {
109
            throw new TDBMInvalidOffsetException('Trying to access result set using offset "'.$offset.'". An offset must be a positive integer.');
110
        }
111
        if (!$this->fetchStarted) {
112
            $this->rewind();
113
        }
114
        while (!isset($this->results[$offset])) {
115
            $this->next();
116
            if ($this->current === null) {
117
                throw new TDBMInvalidOffsetException('Offset "'.$offset.'" does not exist in result set.');
118
            }
119
        }
120
    }
121
122
    public function next()
123
    {
124
        // Let's overload the next() method to store the result.
125
        if (isset($this->results[$this->key + 1])) {
126
            ++$this->key;
127
            $this->current = $this->results[$this->key];
128
        } else {
129
            $this->current = next($this->iterator);
0 ignored issues
show
Bug introduced by
$this->iterator of type Traversable is incompatible with the type array expected by parameter $array of next(). ( Ignorable by Annotation )

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

129
            $this->current = next(/** @scrutinizer ignore-type */ $this->iterator);
Loading history...
130
            $this->key = key($this->iterator);
0 ignored issues
show
Documentation Bug introduced by
It seems like key($this->iterator) can also be of type string. However, the property $key 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...
131
            if ($this->key !== null) {
132
                $this->results[$this->key] = $this->current;
133
            }
134
        }
135
    }
136
137
    /**
138
     * Do not reexecute the query.
139
     */
140
    public function rewind()
141
    {
142
        if (!$this->fetchStarted) {
143
            reset($this->iterator);
0 ignored issues
show
Bug introduced by
$this->iterator of type Traversable is incompatible with the type array expected by parameter $array of reset(). ( Ignorable by Annotation )

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

143
            reset(/** @scrutinizer ignore-type */ $this->iterator);
Loading history...
144
            $this->fetchStarted = true;
145
            $this->key = key($this->iterator);
0 ignored issues
show
Documentation Bug introduced by
It seems like key($this->iterator) can also be of type string. However, the property $key 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...
146
            $this->current = current($this->iterator);
147
            $this->results[$this->key] = $this->current;
148
        } else {
149
            $this->key = 0;
150
            $this->current = $this->results[0];
151
        }
152
    }
153
154
    /**
155
     * Return the current element
156
     * @link https://php.net/manual/en/iterator.current.php
157
     * @return mixed Can return any type.
158
     * @since 5.0.0
159
     */
160
    public function current()
161
    {
162
        return $this->current;
163
    }
164
165
    /**
166
     * Return the key of the current element
167
     * @link https://php.net/manual/en/iterator.key.php
168
     * @return mixed scalar on success, or null on failure.
169
     * @since 5.0.0
170
     */
171
    public function key()
172
    {
173
        return $this->key;
174
    }
175
176
    /**
177
     * Checks if current position is valid
178
     * @link https://php.net/manual/en/iterator.valid.php
179
     * @return boolean The return value will be casted to boolean and then evaluated.
180
     * Returns true on success or false on failure.
181
     * @since 5.0.0
182
     */
183
    public function valid()
184
    {
185
        return $this->key !== null;
186
    }
187
188
    /**
189
     * Offset to set
190
     * @link https://php.net/manual/en/arrayaccess.offsetset.php
191
     * @param mixed $offset <p>
192
     * The offset to assign the value to.
193
     * </p>
194
     * @param mixed $value <p>
195
     * The value to set.
196
     * </p>
197
     * @return void
198
     * @since 5.0.0
199
     */
200
    public function offsetSet($offset, $value)
201
    {
202
        throw new TDBMInvalidOperationException('You cannot set values in a TDBM result set.');
203
    }
204
205
    /**
206
     * Offset to unset
207
     * @link https://php.net/manual/en/arrayaccess.offsetunset.php
208
     * @param mixed $offset <p>
209
     * The offset to unset.
210
     * </p>
211
     * @return void
212
     * @since 5.0.0
213
     */
214
    public function offsetUnset($offset)
215
    {
216
        throw new TDBMInvalidOperationException('You cannot unset values in a TDBM result set.');
217
    }
218
219
    /**
220
     * Count elements of an object
221
     * @link https://php.net/manual/en/countable.count.php
222
     * @return int The custom count as an integer.
223
     * </p>
224
     * <p>
225
     * The return value is cast to an integer.
226
     * @since 5.1.0
227
     */
228
    public function count()
229
    {
230
        if ($this->iterator instanceof Countable) {
231
            return $this->iterator->count();
232
        }
233
        throw new TDBMInvalidOperationException('Cannot count items of iterator '.get_class($this->iterator));
234
    }
235
}
236