Passed
Branch psalm-3 (47cb7b)
by Wilmer
02:25
created

DataReader::readAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Query\Data;
6
7
use Countable;
8
use Iterator;
9
use PDO;
10
use PDOStatement;
11
use Yiisoft\Db\Command\CommandInterface;
12
use Yiisoft\Db\Exception\InvalidCallException;
13
14
use function call_user_func_array;
15
16
/**
17
 * DataReader represents a forward-only stream of rows from a query result set.
18
 *
19
 * To read the current row of data, call {@see read()}. The method {@see readAll()} returns all the rows in a single
20
 * array. Rows of data can also be read by iterating through the reader. For example,
21
 *
22
 * ```php
23
 * $command = $connection->createCommand('SELECT * FROM post');
24
 * $reader = $command->query();
25
 *
26
 * while ($row = $reader->read()) {
27
 *     $rows[] = $row;
28
 * }
29
 *
30
 * // equivalent to:
31
 * foreach ($reader as $row) {
32
 *     $rows[] = $row;
33
 * }
34
 *
35
 * // equivalent to:
36
 * $rows = $reader->readAll();
37
 * ```
38
 *
39
 * Note that since DataReader is a forward-only stream, you can only traverse it once. Doing it the second time will
40
 * throw an exception.
41
 *
42
 * It is possible to use a specific mode of data fetching by setting {@see fetchMode}. See the
43
 * [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) for more details about possible
44
 * fetch mode.
45
 *
46
 * @property int $columnCount The number of columns in the result set. This property is read-only.
47
 * @property int $fetchMode Fetch mode. This property is write-only.
48
 * @property bool $isClosed Whether the reader is closed or not. This property is read-only.
49
 * @property int $rowCount Number of rows contained in the result. This property is read-only.
50
 */
51
final class DataReader implements Iterator, Countable
52
{
53
    private bool $closed = false;
54
    private int $index = -1;
55
    private mixed $row;
56
    private ?PDOStatement $statement = null;
57
58
    public function __construct(CommandInterface $command)
59
    {
60
        $this->statement = $command->getPDOStatement();
61
        $this->getPDOStatement()->setFetchMode(PDO::FETCH_ASSOC);
62
    }
63
64
    /**
65
     * Binds a column to a PHP variable.
66
     *
67
     * When rows of data are being fetched, the corresponding column value will be set in the variable. Note, the fetch
68
     * mode must include {@see PDO::FETCH_BOUND}.
69
     *
70
     * @param int|string $column Number of the column (1-indexed) or name of the column in the result set. If using the
71
     * column name, be aware that the name should match the case of the column, as returned by the driver.
72
     * @param mixed $value Name of the PHP variable to which the column will be bound.
73
     * @param int|null $dataType Data type of the parameter.
74
     *
75
     * {@see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php}
76
     */
77
    public function bindColumn($column, &$value, ?int $dataType = null): void
78
    {
79
        if ($dataType === null) {
80
            $this->getPDOStatement()->bindColumn($column, $value);
81
        } else {
82
            $this->getPDOStatement()->bindColumn($column, $value, $dataType);
83
        }
84
    }
85
86
    /**
87
     * Set the default fetch mode for this statement.
88
     *
89
     * @param int $mode fetch mode.
90
     *
91
     * {@see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php}
92
     */
93
    public function setFetchMode(int $mode): void
94
    {
95
        $params = func_get_args();
96
        call_user_func_array([$this->getPDOStatement(), 'setFetchMode'], $params);
97
    }
98
99
    /**
100
     * Advances the reader to the next row in a result set.
101
     *
102
     * @return array|bool the current row, false if no more row available.
103
     */
104
    public function read(): array|bool
105
    {
106
        return $this->getPDOStatement()->fetch();
107
    }
108
109
    /**
110
     * Returns a single column from the next row of a result set.
111
     *
112
     * @param int $columnIndex zero-based column index.
113
     *
114
     * @return mixed the column of the current row, false if no more rows available.
115
     */
116
    public function readColumn(int $columnIndex): mixed
117
    {
118
        return $this->getPDOStatement()->fetchColumn($columnIndex);
119
    }
120
121
    /**
122
     * Returns an object populated with the next row of data.
123
     *
124
     * @param string $className class name of the object to be created and populated.
125
     * @param array $fields Elements of this array are passed to the constructor.
126
     *
127
     * @return mixed the populated object, false if no more row of data available.
128
     *
129
     * @psalm-param class-string $className
130
     */
131
    public function readObject(string $className, array $fields): mixed
132
    {
133
        return $this->getPDOStatement()->fetchObject($className, $fields);
134
    }
135
136
    /**
137
     * Reads the whole result set into an array.
138
     *
139
     * @return array the result set (each array element represents a row of data). An empty array will be returned if
140
     * the result contains no row.
141
     */
142
    public function readAll(): array
143
    {
144
        return $this->getPDOStatement()->fetchAll();
145
    }
146
147
    /**
148
     * Advances the reader to the next result when reading the results of a batch of statements. This method is only
149
     * useful when there are multiple result sets returned by the query. Not all DBMS support this feature.
150
     *
151
     * @return bool Returns true on success or false on failure.
152
     */
153
    public function nextResult(): bool
154
    {
155
        if (($result = $this->getPDOStatement()->nextRowset()) !== false) {
156
            $this->index = -1;
157
        }
158
159
        return $result;
160
    }
161
162
    /**
163
     * Closes the reader.
164
     *
165
     * This frees up the resources allocated for executing this SQL statement. Read attempts after this method call are
166
     * unpredictable.
167
     */
168
    public function close(): void
169
    {
170
        $this->getPDOStatement()->closeCursor();
171
        $this->closed = true;
172
    }
173
174
    /**
175
     * whether the reader is closed or not.
176
     *
177
     * @return bool whether the reader is closed or not.
178
     */
179
    public function isClosed(): bool
180
    {
181
        return $this->closed;
182
    }
183
184
    /**
185
     * Returns the number of rows in the result set.
186
     *
187
     * Note, most DBMS may not give a meaningful count. In this case, use "SELECT COUNT(*) FROM tableName" to obtain the
188
     * number of rows.
189
     *
190
     * @return int number of rows contained in the result.
191
     */
192
    public function getRowCount(): int
193
    {
194
        return $this->getPDOStatement()->rowCount();
195
    }
196
197
    /**
198
     * Returns the number of rows in the result set.
199
     *
200
     * This method is required by the Countable interface.
201
     *
202
     * Note, most DBMS may not give a meaningful count. In this case, use "SELECT COUNT(*) FROM tableName" to obtain the
203
     * number of rows.
204
     *
205
     * @return int number of rows contained in the result.
206
     */
207
    public function count(): int
208
    {
209
        return $this->getRowCount();
210
    }
211
212
    /**
213
     * Returns the number of columns in the result set.
214
     *
215
     * Note, even there's no row in the reader, this still gives correct column number.
216
     *
217
     * @return int the number of columns in the result set.
218
     */
219
    public function getColumnCount(): int
220
    {
221
        return $this->getPDOStatement()->columnCount();
222
    }
223
224
    /**
225
     * Resets the iterator to the initial state.
226
     *
227
     * This method is required by the interface {@see Iterator}.
228
     *
229
     * @throws InvalidCallException
230
     */
231
    public function rewind(): void
232
    {
233
        if ($this->index < 0) {
234
            $this->row = $this->getPDOStatement()->fetch();
235
            $this->index = 0;
236
        } else {
237
            throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.');
238
        }
239
    }
240
241
    /**
242
     * Returns the index of the current row.
243
     *
244
     * This method is required by the interface {@see Iterator}.
245
     *
246
     * @return int the index of the current row.
247
     */
248
    public function key(): int
249
    {
250
        return $this->index;
251
    }
252
253
    /**
254
     * Returns the current row.
255
     *
256
     * This method is required by the interface {@see Iterator}.
257
     *
258
     * @return mixed the current row.
259
     */
260
    #[\ReturnTypeWillChange]
261
    public function current(): mixed
262
    {
263
        return $this->row;
264
    }
265
266
    /**
267
     * Moves the internal pointer to the next row.
268
     *
269
     * This method is required by the interface {@see Iterator}.
270
     */
271
    public function next(): void
272
    {
273
        $this->row = $this->getPDOStatement()->fetch();
274
        $this->index++;
275
    }
276
277
    /**
278
     * Returns whether there is a row of data at current position.
279
     *
280
     * This method is required by the interface {@see Iterator}.
281
     *
282
     * @return bool whether there is a row of data at current position.
283
     */
284
    public function valid(): bool
285
    {
286
        return $this->row !== false;
287
    }
288
289
    public function getPDOStatement(): PDOStatement
290
    {
291
        if ($this->statement === null) {
292
            throw new InvalidCallException('The PDOStatement cannot be null.');
293
        }
294
295
        return $this->statement;
296
    }
297
}
298