class StatementPrefetchIterator

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Database/StatementPrefetchIterator.php \Drupal\Core\Database\StatementPrefetchIterator

An implementation of StatementInterface that prefetches all data.

This class behaves very similar to a StatementWrapperIterator of a \PDOStatement but as it always fetches every row it is possible to manipulate those results.

Hierarchy

Expanded class hierarchy of StatementPrefetchIterator

2 files declare their use of StatementPrefetchIterator
ConnectionTest.php in core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
Statement.php in core/modules/sqlite/src/Driver/Database/sqlite/Statement.php

File

core/lib/Drupal/Core/Database/StatementPrefetchIterator.php, line 16

Namespace

Drupal\Core\Database
View source
class StatementPrefetchIterator implements \Iterator, StatementInterface {
    use StatementIteratorTrait;
    use FetchModeTrait;
    
    /**
     * Main data store.
     *
     * The resultset is stored as a \PDO::FETCH_ASSOC array.
     */
    protected array $data = [];
    
    /**
     * The list of column names in this result set.
     *
     * @var string[]
     */
    protected ?array $columnNames = NULL;
    
    /**
     * The number of rows matched by the last query.
     */
    protected ?int $rowCount = NULL;
    
    /**
     * Holds the default fetch style.
     */
    protected int $defaultFetchStyle = \PDO::FETCH_OBJ;
    
    /**
     * Holds fetch options.
     *
     * @var string[]
     */
    protected array $fetchOptions = [
        'class' => 'stdClass',
        'constructor_args' => [],
        'object' => NULL,
        'column' => 0,
    ];
    
    /**
     * Constructs a StatementPrefetchIterator object.
     *
     * @param object $clientConnection
     *   Client database connection object, for example \PDO.
     * @param \Drupal\Core\Database\Connection $connection
     *   The database connection.
     * @param string $queryString
     *   The query string.
     * @param array $driverOptions
     *   Driver-specific options.
     * @param bool $rowCountEnabled
     *   (optional) Enables counting the rows matched. Defaults to FALSE.
     */
    public function __construct(object $clientConnection, Connection $connection, string $queryString, array $driverOptions = [], bool $rowCountEnabled = FALSE) {
    }
    
    /**
     * {@inheritdoc}
     */
    public function getConnectionTarget() : string {
        return $this->connection
            ->getTarget();
    }
    
    /**
     * {@inheritdoc}
     */
    public function execute($args = [], $options = []) {
        if (isset($options['fetch'])) {
            if (is_string($options['fetch'])) {
                // Default to an object. Note: db fields will be added to the object
                // before the constructor is run. If you need to assign fields after
                // the constructor is run. See https://www.drupal.org/node/315092.
                $this->setFetchMode(\PDO::FETCH_CLASS, $options['fetch']);
            }
            else {
                $this->setFetchMode($options['fetch']);
            }
        }
        if ($this->connection
            ->isEventEnabled(StatementExecutionStartEvent::class)) {
            $startEvent = new StatementExecutionStartEvent(spl_object_id($this), $this->connection
                ->getKey(), $this->connection
                ->getTarget(), $this->getQueryString(), $args ?? [], $this->connection
                ->findCallerFromDebugBacktrace());
            $this->connection
                ->dispatchEvent($startEvent);
        }
        // Prepare and execute the statement.
        try {
            $statement = $this->getStatement($this->queryString, $args);
            $return = $statement->execute($args);
        } catch (\Exception $e) {
            if (isset($startEvent) && $this->connection
                ->isEventEnabled(StatementExecutionFailureEvent::class)) {
                $this->connection
                    ->dispatchEvent(new StatementExecutionFailureEvent($startEvent->statementObjectId, $startEvent->key, $startEvent->target, $startEvent->queryString, $startEvent->args, $startEvent->caller, $startEvent->time, get_class($e), $e->getCode(), $e->getMessage()));
            }
            throw $e;
        }
        // Fetch all the data from the reply, in order to release any lock as soon
        // as possible.
        $this->data = $statement->fetchAll(\PDO::FETCH_ASSOC);
        $this->rowCount = $this->rowCountEnabled ? $statement->rowCount() : NULL;
        // Destroy the statement as soon as possible. See the documentation of
        // \Drupal\sqlite\Driver\Database\sqlite\Statement for an explanation.
        unset($statement);
        $this->markResultsetIterable($return);
        $this->columnNames = count($this->data) > 0 ? array_keys($this->data[0]) : [];
        if (isset($startEvent) && $this->connection
            ->isEventEnabled(StatementExecutionEndEvent::class)) {
            $this->connection
                ->dispatchEvent(new StatementExecutionEndEvent($startEvent->statementObjectId, $startEvent->key, $startEvent->target, $startEvent->queryString, $startEvent->args, $startEvent->caller, $startEvent->time));
        }
        return $return;
    }
    
    /**
     * Throw a PDO Exception based on the last PDO error.
     *
     * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is
     *   no replacement.
     *
     * @see https://www.drupal.org/node/3410663
     */
    protected function throwPDOException() : void {
        @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3410663', E_USER_DEPRECATED);
        $error_info = $this->connection
            ->errorInfo();
        // We rebuild a message formatted in the same way as PDO.
        $exception = new \PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
        $exception->errorInfo = $error_info;
        throw $exception;
    }
    
    /**
     * Grab a PDOStatement object from a given query and its arguments.
     *
     * Some drivers (including SQLite) will need to perform some preparation
     * themselves to get the statement right.
     *
     * @param $query
     *   The query.
     * @param array|null $args
     *   An array of arguments. This can be NULL.
     *
     * @return object
     *   A PDOStatement object.
     */
    protected function getStatement(string $query, ?array &$args = []) : object {
        return $this->connection
            ->prepare($query, $this->driverOptions);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getQueryString() {
        return $this->queryString;
    }
    
    /**
     * {@inheritdoc}
     */
    public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
        if (!in_array($mode, $this->supportedFetchModes)) {
            @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
        }
        $this->defaultFetchStyle = $mode;
        switch ($mode) {
            case \PDO::FETCH_CLASS:
                $this->fetchOptions['class'] = $a1;
                if ($a2) {
                    $this->fetchOptions['constructor_args'] = $a2;
                }
                break;
            case \PDO::FETCH_COLUMN:
                $this->fetchOptions['column'] = $a1;
                break;
            case \PDO::FETCH_INTO:
                $this->fetchOptions['object'] = $a1;
                break;
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function rowCount() {
        // SELECT query should not use the method.
        if ($this->rowCountEnabled) {
            return $this->rowCount;
        }
        else {
            throw new RowCountException();
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
        $currentKey = $this->getResultsetCurrentRowIndex();
        // We can remove the current record from the prefetched data, before
        // moving to the next record.
        unset($this->data[$currentKey]);
        $currentKey++;
        if (!isset($this->data[$currentKey])) {
            $this->markResultsetFetchingComplete();
            return FALSE;
        }
        // Now, format the next prefetched record according to the required fetch
        // style.
        // @todo in Drupal 11, remove arms for deprecated fetch modes.
        $rowAssoc = $this->data[$currentKey];
        $row = match ($fetch_style ?? $this->defaultFetchStyle) {    \PDO::FETCH_ASSOC => $rowAssoc,
            \PDO::FETCH_BOTH => $this->assocToBoth($rowAssoc),
            \PDO::FETCH_NUM => $this->assocToNum($rowAssoc),
            \PDO::FETCH_LAZY, \PDO::FETCH_OBJ => $this->assocToObj($rowAssoc),
            \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE => $this->assocToClassType($rowAssoc, $this->fetchOptions['constructor_args']),
            \PDO::FETCH_CLASS => $this->assocToClass($rowAssoc, $this->fetchOptions['class'], $this->fetchOptions['constructor_args']),
            \PDO::FETCH_INTO => $this->assocIntoObject($rowAssoc, $this->fetchOptions['object']),
            \PDO::FETCH_COLUMN => $this->assocToColumn($rowAssoc, $this->columnNames, $this->fetchOptions['column']),
            default => FALSE,
        
        };
        $this->setResultsetCurrentRow($row);
        return $row;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchColumn($index = 0) {
        if ($row = $this->fetch(\PDO::FETCH_ASSOC)) {
            return $row[$this->columnNames[$index]];
        }
        return FALSE;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchField($index = 0) {
        return $this->fetchColumn($index);
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchObject(?string $class_name = NULL, array $constructor_arguments = []) {
        if (!isset($class_name)) {
            return $this->fetch(\PDO::FETCH_OBJ);
        }
        $this->fetchOptions = [
            'class' => $class_name,
            'constructor_args' => $constructor_arguments,
        ];
        return $this->fetch(\PDO::FETCH_CLASS);
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAssoc() {
        return $this->fetch(\PDO::FETCH_ASSOC);
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
        if (isset($mode) && !in_array($mode, $this->supportedFetchModes)) {
            @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
        }
        $fetchStyle = $mode ?? $this->defaultFetchStyle;
        if (isset($column_index)) {
            $this->fetchOptions['column'] = $column_index;
        }
        if (isset($constructor_arguments)) {
            $this->fetchOptions['constructor_args'] = $constructor_arguments;
        }
        $result = [];
        while ($row = $this->fetch($fetchStyle)) {
            $result[] = $row;
        }
        return $result;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchCol($index = 0) {
        if (isset($this->columnNames[$index])) {
            $result = [];
            while ($row = $this->fetch(\PDO::FETCH_ASSOC)) {
                $result[] = $row[$this->columnNames[$index]];
            }
            return $result;
        }
        return [];
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAllKeyed($key_index = 0, $value_index = 1) {
        if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index])) {
            return [];
        }
        $key = $this->columnNames[$key_index];
        $value = $this->columnNames[$value_index];
        $result = [];
        while ($row = $this->fetch(\PDO::FETCH_ASSOC)) {
            $result[$row[$key]] = $row[$value];
        }
        return $result;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAllAssoc($key, $fetch_style = NULL) {
        $fetchStyle = $fetch_style ?? $this->defaultFetchStyle;
        $result = [];
        while ($row = $this->fetch($fetchStyle)) {
            $result[$this->data[$this->getResultsetCurrentRowIndex()][$key]] = $row;
        }
        return $result;
    }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
FetchModeTrait::$fetchModeLiterals protected property Map FETCH_* modes to their literal for inclusion in messages.
FetchModeTrait::$supportedFetchModes protected property The fetch modes supported.
FetchModeTrait::assocIntoObject Deprecated protected function Fills an object with data from a FETCH_ASSOC row.
FetchModeTrait::assocToBoth Deprecated protected function Converts a row of data in FETCH_ASSOC format to FETCH_BOTH.
FetchModeTrait::assocToClass protected function Converts a row of data in FETCH_ASSOC format to FETCH_CLASS.
FetchModeTrait::assocToClassType Deprecated protected function Converts a row of data to FETCH_CLASS | FETCH_CLASSTYPE.
FetchModeTrait::assocToColumn protected function Converts a row of data in FETCH_ASSOC format to FETCH_COLUMN.
FetchModeTrait::assocToNum protected function Converts a row of data in FETCH_ASSOC format to FETCH_NUM.
FetchModeTrait::assocToObj protected function Converts a row of data in FETCH_ASSOC format to FETCH_OBJ.
StatementIteratorTrait::$isResultsetIterable private property Traces if rows can be fetched from the resultset.
StatementIteratorTrait::$resultsetKey private property The key of the current row.
StatementIteratorTrait::$resultsetRow private property The current row, retrieved in the current fetch format.
StatementIteratorTrait::current public function Returns the current element.
StatementIteratorTrait::getResultsetCurrentRowIndex protected function Returns the row index of the current element in the resultset.
StatementIteratorTrait::key public function Returns the key of the current element.
StatementIteratorTrait::markResultsetFetchingComplete protected function Informs the iterator that no more rows can be fetched from the resultset.
StatementIteratorTrait::markResultsetIterable protected function Informs the iterator whether rows can be fetched from the resultset.
StatementIteratorTrait::next public function Moves the current position to the next element.
StatementIteratorTrait::rewind public function Rewinds back to the first element of the Iterator.
StatementIteratorTrait::setResultsetCurrentRow protected function Sets the current resultset row for the iterator, and increments the key.
StatementIteratorTrait::valid public function Checks if current position is valid.
StatementPrefetchIterator::$columnNames protected property The list of column names in this result set.
StatementPrefetchIterator::$data protected property Main data store.
StatementPrefetchIterator::$defaultFetchStyle protected property Holds the default fetch style.
StatementPrefetchIterator::$fetchOptions protected property Holds fetch options.
StatementPrefetchIterator::$rowCount protected property The number of rows matched by the last query.
StatementPrefetchIterator::execute public function Executes a prepared statement. Overrides StatementInterface::execute 1
StatementPrefetchIterator::fetch public function Fetches the next row from a result set. Overrides StatementInterface::fetch
StatementPrefetchIterator::fetchAll public function Returns an array containing all of the result set rows. Overrides StatementInterface::fetchAll
StatementPrefetchIterator::fetchAllAssoc public function Returns the result set as an associative array keyed by the given field. Overrides StatementInterface::fetchAllAssoc
StatementPrefetchIterator::fetchAllKeyed public function Returns the entire result set as a single associative array. Overrides StatementInterface::fetchAllKeyed
StatementPrefetchIterator::fetchAssoc public function Fetches the next row and returns it as an associative array. Overrides StatementInterface::fetchAssoc
StatementPrefetchIterator::fetchCol public function Returns an entire single column of a result set as an indexed array. Overrides StatementInterface::fetchCol
StatementPrefetchIterator::fetchColumn public function
StatementPrefetchIterator::fetchField public function Returns a single field from the next record of a result set. Overrides StatementInterface::fetchField
StatementPrefetchIterator::fetchObject public function Fetches the next row and returns it as an object. Overrides StatementInterface::fetchObject
StatementPrefetchIterator::getConnectionTarget public function Returns the target connection this statement is associated with. Overrides StatementInterface::getConnectionTarget
StatementPrefetchIterator::getQueryString public function Gets the query string of this statement. Overrides StatementInterface::getQueryString
StatementPrefetchIterator::getStatement protected function Grab a PDOStatement object from a given query and its arguments. 1
StatementPrefetchIterator::rowCount public function Returns the number of rows matched by the last SQL statement. Overrides StatementInterface::rowCount
StatementPrefetchIterator::setFetchMode public function Sets the default fetch mode for this statement. Overrides StatementInterface::setFetchMode
StatementPrefetchIterator::throwPDOException Deprecated protected function Throw a PDO Exception based on the last PDO error.
StatementPrefetchIterator::__construct public function Constructs a StatementPrefetchIterator object.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.