*/ class BufferedStatement implements Iterator, StatementInterface { use TypeConverterTrait; /** * If true, all rows were fetched * * @var bool */ protected $_allFetched = false; /** * The decorated statement * * @var \Cake\Database\StatementInterface */ protected $statement; /** * The driver for the statement * * @var \Cake\Database\DriverInterface */ protected $_driver; /** * The in-memory cache containing results from previous iterators * * @var array */ protected $buffer = []; /** * Whether this statement has already been executed * * @var bool */ protected $_hasExecuted = false; /** * The current iterator index. * * @var int */ protected $index = 0; /** * Constructor * * @param \Cake\Database\StatementInterface $statement Statement implementation such as PDOStatement * @param \Cake\Database\DriverInterface $driver Driver instance */ public function __construct(StatementInterface $statement, DriverInterface $driver) { $this->statement = $statement; $this->_driver = $driver; } /** * Returns the connection driver. * * @return \Cake\Database\DriverInterface */ protected function getDriver(): DriverInterface { return $this->_driver; } /** * Magic getter to return $queryString as read-only. * * @param string $property internal property to get * @return string|null */ public function __get(string $property) { if ($property === 'queryString') { /** @psalm-suppress NoInterfaceProperties */ return $this->statement->queryString; } return null; } /** * @inheritDoc */ public function bindValue($column, $value, $type = 'string'): void { $this->statement->bindValue($column, $value, $type); } /** * @inheritDoc */ public function closeCursor(): void { $this->statement->closeCursor(); } /** * @inheritDoc */ public function columnCount(): int { return $this->statement->columnCount(); } /** * @inheritDoc */ public function errorCode() { return $this->statement->errorCode(); } /** * @inheritDoc */ public function errorInfo(): array { return $this->statement->errorInfo(); } /** * @inheritDoc */ public function execute(?array $params = null): bool { $this->_reset(); $this->_hasExecuted = true; return $this->statement->execute($params); } /** * @inheritDoc */ public function fetchColumn(int $position) { $result = $this->fetch(static::FETCH_TYPE_NUM); if ($result !== false && isset($result[$position])) { return $result[$position]; } return false; } /** * Statements can be passed as argument for count() to return the number * for affected rows from last execution. * * @return int */ public function count(): int { return $this->rowCount(); } /** * @inheritDoc */ public function bind(array $params, array $types): void { $this->statement->bind($params, $types); } /** * @inheritDoc */ public function lastInsertId(?string $table = null, ?string $column = null) { return $this->statement->lastInsertId($table, $column); } /** * {@inheritDoc} * * @param string|int $type The type to fetch. * @return array|false */ public function fetch($type = self::FETCH_TYPE_NUM) { if ($this->_allFetched) { $row = false; if (isset($this->buffer[$this->index])) { $row = $this->buffer[$this->index]; } $this->index += 1; if ($row && $type === static::FETCH_TYPE_NUM) { return array_values($row); } return $row; } $record = $this->statement->fetch($type); if ($record === false) { $this->_allFetched = true; $this->statement->closeCursor(); return false; } $this->buffer[] = $record; return $record; } /** * @return array */ public function fetchAssoc(): array { $result = $this->fetch(static::FETCH_TYPE_ASSOC); return $result ?: []; } /** * @inheritDoc */ public function fetchAll($type = self::FETCH_TYPE_NUM) { if ($this->_allFetched) { return $this->buffer; } $results = $this->statement->fetchAll($type); if ($results !== false) { $this->buffer = array_merge($this->buffer, $results); } $this->_allFetched = true; $this->statement->closeCursor(); return $this->buffer; } /** * @inheritDoc */ public function rowCount(): int { if (!$this->_allFetched) { $this->fetchAll(static::FETCH_TYPE_ASSOC); } return count($this->buffer); } /** * Reset all properties * * @return void */ protected function _reset(): void { $this->buffer = []; $this->_allFetched = false; $this->index = 0; } /** * Returns the current key in the iterator * * @return mixed */ #[\ReturnTypeWillChange] public function key() { return $this->index; } /** * Returns the current record in the iterator * * @return mixed */ #[\ReturnTypeWillChange] public function current() { return $this->buffer[$this->index]; } /** * Rewinds the collection * * @return void */ public function rewind(): void { $this->index = 0; } /** * Returns whether the iterator has more elements * * @return bool */ public function valid(): bool { $old = $this->index; $row = $this->fetch(self::FETCH_TYPE_ASSOC); // Restore the index as fetch() increments during // the cache scenario. $this->index = $old; return $row !== false; } /** * Advances the iterator pointer to the next element * * @return void */ public function next(): void { $this->index += 1; } /** * Get the wrapped statement * * @return \Cake\Database\StatementInterface */ public function getInnerStatement(): StatementInterface { return $this->statement; } }