362 lines
7.7 KiB
PHP
362 lines
7.7 KiB
PHP
|
<?php
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
/**
|
||
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||
|
*
|
||
|
* Licensed under The MIT License
|
||
|
* For full copyright and license information, please see the LICENSE.txt
|
||
|
* Redistributions of files must retain the above copyright notice.
|
||
|
*
|
||
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||
|
* @link https://cakephp.org CakePHP(tm) Project
|
||
|
* @since 3.0.0
|
||
|
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||
|
*/
|
||
|
namespace Cake\Database\Statement;
|
||
|
|
||
|
use Cake\Database\DriverInterface;
|
||
|
use Cake\Database\StatementInterface;
|
||
|
use Cake\Database\TypeConverterTrait;
|
||
|
use Iterator;
|
||
|
|
||
|
/**
|
||
|
* A statement decorator that implements buffered results.
|
||
|
*
|
||
|
* This statement decorator will save fetched results in memory, allowing
|
||
|
* the iterator to be rewound and reused.
|
||
|
*
|
||
|
* @template-implements \Iterator<mixed>
|
||
|
*/
|
||
|
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<int, 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;
|
||
|
}
|
||
|
}
|