Passed
Pull Request — master (#266)
by Wilmer
10:46
created

DataReader::read()   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\Command;
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
    /** @var mixed @row */
54
    private $row;
55
    private PDOStatement $statement;
56
    private bool $closed = false;
57
    private int $index = -1;
58
59
    public function __construct(Command $command)
60
    {
61
        $this->statement = $command->getPDOStatement();
62
        $this->statement->setFetchMode(PDO::FETCH_ASSOC);
63
    }
64
65
    /**
66
     * Binds a column to a PHP variable.
67
     *
68
     * When rows of data are being fetched, the corresponding column value will be set in the variable. Note, the fetch
69
     * mode must include {@see PDO::FETCH_BOUND}.
70
     *
71
     * @param int|string $column Number of the column (1-indexed) or name of the column in the result set. If using the
72
     * column name, be aware that the name should match the case of the column, as returned by the driver.
73
     * @param mixed $value Name of the PHP variable to which the column will be bound.
74
     * @param int|null $dataType Data type of the parameter.
75
     *
76
     * {@see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php}
77
     */
78
    public function bindColumn($column, &$value, ?int $dataType = null): void
79
    {
80
        if ($dataType === null) {
81
            $this->statement->bindColumn($column, $value);
82
        } else {
83
            $this->statement->bindColumn($column, $value, $dataType);
84
        }
85
    }
86
87
    /**
88
     * Set the default fetch mode for this statement.
89
     *
90
     * @param int $mode fetch mode.
91
     *
92
     * {@see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php}
93
     */
94
    public function setFetchMode(int $mode): void
95
    {
96
        $params = func_get_args();
97
        call_user_func_array([$this->statement, 'setFetchMode'], $params);
98
    }
99
100
    /**
101
     * Advances the reader to the next row in a result set.
102
     *
103
     * @return array|bool the current row, false if no more row available.
104
     */
105
    public function read()
106
    {
107
        return $this->statement->fetch();
108
    }
109
110
    /**
111
     * Returns a single column from the next row of a result set.
112
     *
113
     * @param int $columnIndex zero-based column index.
114
     *
115
     * @return mixed the column of the current row, false if no more rows available.
116
     */
117
    public function readColumn(int $columnIndex)
118
    {
119
        return $this->statement->fetchColumn($columnIndex);
120
    }
121
122
    /**
123
     * Returns an object populated with the next row of data.
124
     *
125
     * @param string $className class name of the object to be created and populated.
126
     * @param array $fields Elements of this array are passed to the constructor.
127
     *
128
     * @return mixed the populated object, false if no more row of data available.
129
     */
130
    public function readObject(string $className, array $fields)
131
    {
132
        return $this->statement->fetchObject($className, $fields);
133
    }
134
135
    /**
136
     * Reads the whole result set into an array.
137
     *
138
     * @return array the result set (each array element represents a row of data). An empty array will be returned if
139
     * the result contains no row.
140
     */
141
    public function readAll(): array
142
    {
143
        return $this->statement->fetchAll();
144
    }
145
146
    /**
147
     * Advances the reader to the next result when reading the results of a batch of statements. This method is only
148
     * useful when there are multiple result sets returned by the query. Not all DBMS support this feature.
149
     *
150
     * @return bool Returns true on success or false on failure.
151
     */
152
    public function nextResult(): bool
153
    {
154
        if (($result = $this->statement->nextRowset()) !== false) {
155
            $this->index = -1;
156
        }
157
158
        return $result;
159
    }
160
161
    /**
162
     * Closes the reader.
163
     *
164
     * This frees up the resources allocated for executing this SQL statement. Read attempts after this method call are
165
     * unpredictable.
166
     */
167
    public function close(): void
168
    {
169
        $this->statement->closeCursor();
170
        $this->closed = true;
171
    }
172
173
    /**
174
     * whether the reader is closed or not.
175
     *
176
     * @return bool whether the reader is closed or not.
177
     */
178
    public function isClosed(): bool
179
    {
180
        return $this->closed;
181
    }
182
183
    /**
184
     * Returns the number of rows in the result set.
185
     *
186
     * Note, most DBMS may not give a meaningful count. In this case, use "SELECT COUNT(*) FROM tableName" to obtain the
187
     * number of rows.
188
     *
189
     * @return int number of rows contained in the result.
190
     */
191
    public function getRowCount(): int
192
    {
193
        return $this->statement->rowCount();
194
    }
195
196
    /**
197
     * Returns the number of rows in the result set.
198
     *
199
     * This method is required by the Countable interface.
200
     *
201
     * Note, most DBMS may not give a meaningful count. In this case, use "SELECT COUNT(*) FROM tableName" to obtain the
202
     * number of rows.
203
     *
204
     * @return int number of rows contained in the result.
205
     */
206
    public function count(): int
207
    {
208
        return $this->getRowCount();
209
    }
210
211
    /**
212
     * Returns the number of columns in the result set.
213
     *
214
     * Note, even there's no row in the reader, this still gives correct column number.
215
     *
216
     * @return int the number of columns in the result set.
217
     */
218
    public function getColumnCount(): int
219
    {
220
        return $this->statement->columnCount();
221
    }
222
223
    /**
224
     * Resets the iterator to the initial state.
225
     *
226
     * This method is required by the interface {@see Iterator}.
227
     *
228
     * @throws InvalidCallException
229
     */
230
    public function rewind(): void
231
    {
232
        if ($this->index < 0) {
233
            $this->row = $this->statement->fetch();
234
            $this->index = 0;
235
        } else {
236
            throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.');
237
        }
238
    }
239
240
    /**
241
     * Returns the index of the current row.
242
     *
243
     * This method is required by the interface {@see Iterator}.
244
     *
245
     * @return int the index of the current row.
246
     */
247
    public function key(): int
248
    {
249
        return $this->index;
250
    }
251
252
    /**
253
     * Returns the current row.
254
     *
255
     * This method is required by the interface {@see Iterator}.
256
     *
257
     * @return mixed the current row.
258
     */
259
    #[\ReturnTypeWillChange]
260
    public function current()
261
    {
262
        return $this->row;
263
    }
264
265
    /**
266
     * Moves the internal pointer to the next row.
267
     *
268
     * This method is required by the interface {@see Iterator}.
269
     */
270
    public function next(): void
271
    {
272
        $this->row = $this->statement->fetch();
273
        $this->index++;
274
    }
275
276
    /**
277
     * Returns whether there is a row of data at current position.
278
     *
279
     * This method is required by the interface {@see Iterator}.
280
     *
281
     * @return bool whether there is a row of data at current position.
282
     */
283
    public function valid(): bool
284
    {
285
        return $this->row !== false;
286
    }
287
}
288