class StatementBase

StatementInterface base implementation.

This class is meant to be generic enough for any type of database client, even if all Drupal core database drivers currently use PDO clients. We implement \Iterator instead of \IteratorAggregate to allow iteration to be kept in sync with the underlying database resultset cursor. PDO is not able to execute a database operation while a cursor is open on the result of an earlier select query, so Drupal by default uses buffered queries setting \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY to TRUE on the connection. This forces the query to return all the results in a buffer local to the client library, potentially leading to memory issues in case of large datasets being returned by a query. Other database clients, however, could allow multithread queries, or developers could disable buffered queries in PDO: in that case, this class prevents the resultset to be entirely fetched in PHP memory (that an \IteratorAggregate implementation would force) and therefore optimize memory usage while iterating the resultset.

Hierarchy

Expanded class hierarchy of StatementBase

2 files declare their use of StatementBase
StatementPrefetchIterator.php in core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
StatementWrapperIterator.php in core/lib/Drupal/Core/Database/StatementWrapperIterator.php

File

core/lib/Drupal/Core/Database/Statement/StatementBase.php, line 34

Namespace

Drupal\Core\Database\Statement
View source
abstract class StatementBase implements \Iterator, StatementInterface {
    use FetchModeTrait;
    use PdoTrait;
    use StatementIteratorTrait;
    
    /**
     * The client database Statement object.
     *
     * For a \PDO client connection, this will be a \PDOStatement object.
     */
    protected ?object $clientStatement;
    
    /**
     * The results of a data query language (DQL) statement.
     */
    protected ?ResultBase $result = NULL;
    
    /**
     * Holds the default fetch mode.
     */
    protected FetchAs $fetchMode = FetchAs::Object;
    
    /**
     * Holds fetch options.
     *
     * @var array{'class': class-string, 'constructor_args': list<mixed>, 'column': int}
     */
    protected array $fetchOptions = [
        'class' => 'stdClass',
        'constructor_args' => [],
        'column' => 0,
    ];
    
    /**
     * Constructor.
     *
     * @param \Drupal\Core\Database\Connection $connection
     *   Drupal database connection object.
     * @param object $clientConnection
     *   Client database connection object, for example \PDO.
     * @param string $queryString
     *   The query string.
     * @param bool $rowCountEnabled
     *   (optional) Enables counting the rows matched. Defaults to FALSE.
     */
    public function __construct(Connection $connection, object $clientConnection, string $queryString, bool $rowCountEnabled = FALSE) {
    }
    
    /**
     * {@inheritdoc}
     */
    public function getConnectionTarget() : string {
        return $this->connection
            ->getTarget();
    }
    
    /**
     * {@inheritdoc}
     */
    public abstract function execute($args = [], $options = []);
    
    /**
     * Dispatches an event informing that the statement execution begins.
     *
     * @param array $args
     *   An array of values with as many elements as there are bound parameters in
     *   the SQL statement being executed. This can be empty.
     *
     * @return \Drupal\Core\Database\Event\StatementExecutionStartEvent|null
     *   The dispatched event or NULL if event dispatching is not enabled.
     */
    protected function dispatchStatementExecutionStartEvent(array $args) : ?StatementExecutionStartEvent {
        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);
            return $startEvent;
        }
        return NULL;
    }
    
    /**
     * Dispatches an event informing that the statement execution succeeded.
     *
     * @param \Drupal\Core\Database\Event\StatementExecutionStartEvent|null $startEvent
     *   The start event or NULL if event dispatching is not enabled.
     */
    protected function dispatchStatementExecutionEndEvent(?StatementExecutionStartEvent $startEvent) : void {
        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));
        }
    }
    
    /**
     * Dispatches an event informing of the statement execution failure.
     *
     * @param \Drupal\Core\Database\Event\StatementExecutionStartEvent|null $startEvent
     *   The start event or NULL if event dispatching is not enabled.
     * @param \Exception $e
     *   The statement exception thrown.
     */
    protected function dispatchStatementExecutionFailureEvent(?StatementExecutionStartEvent $startEvent, \Exception $e) : void {
        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()));
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function getQueryString() {
        return $this->queryString;
    }
    
    /**
     * {@inheritdoc}
     */
    public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
        if (is_int($mode)) {
            @trigger_error("Passing the \$mode argument as an integer to setFetchMode() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \\Drupal\\Core\\Database\\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
            $mode = $this->pdoToFetchAs($mode);
        }
        assert($mode instanceof FetchAs);
        $this->fetchMode = $mode;
        switch ($mode) {
            case FetchAs::ClassObject:
                $this->fetchOptions['class'] = $a1;
                if ($a2) {
                    $this->fetchOptions['constructor_args'] = $a2;
                }
                break;
            case FetchAs::Column:
                $this->fetchOptions['column'] = $a1;
                break;
        }
        // If the result object is missing, just do with the properties setting.
        try {
            if ($this->result) {
                return $this->result
                    ->setFetchMode($mode, $this->fetchOptions);
            }
            return TRUE;
        } catch (\LogicException) {
            return TRUE;
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetch($mode = NULL, $cursorOrientation = NULL, $cursorOffset = NULL) {
        if (is_int($mode)) {
            @trigger_error("Passing the \$mode argument as an integer to fetch() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \\Drupal\\Core\\Database\\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
            $mode = $this->pdoToFetchAs($mode);
        }
        assert($mode === NULL || $mode instanceof FetchAs);
        $fetchOptions = match (func_num_args()) {    0 => $this->fetchOptions,
            1 => $this->fetchOptions,
            2 => $this->fetchOptions + [
                'cursor_orientation' => $cursorOrientation,
            ],
            default => $this->fetchOptions + [
                'cursor_orientation' => $cursorOrientation,
                'cursor_offset' => $cursorOffset,
            ],
        
        };
        $row = $this->result
            ->fetch($mode ?? $this->fetchMode, $fetchOptions);
        if ($row === FALSE) {
            $this->markResultsetFetchingComplete();
            return FALSE;
        }
        $this->setResultsetCurrentRow($row);
        return $row;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchObject(?string $className = NULL, array $constructorArguments = []) {
        $row = $className === NULL ? $this->result
            ->fetch(FetchAs::Object, []) : $this->result
            ->fetch(FetchAs::ClassObject, [
            'class' => $className,
            'constructor_args' => $constructorArguments,
        ]);
        if ($row === FALSE) {
            $this->markResultsetFetchingComplete();
            return FALSE;
        }
        $this->setResultsetCurrentRow($row);
        return $row;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAssoc() {
        return $this->fetch(FetchAs::Associative);
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchField($index = 0) {
        $column = $this->result
            ->fetch(FetchAs::Column, [
            'column' => $index,
        ]);
        if ($column === FALSE) {
            $this->markResultsetFetchingComplete();
            return FALSE;
        }
        $this->setResultsetCurrentRow($column);
        return $column;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAll($mode = NULL, $columnIndex = NULL, $constructorArguments = NULL) {
        if (is_int($mode)) {
            @trigger_error("Passing the \$mode argument as an integer to fetchAll() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \\Drupal\\Core\\Database\\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
            $mode = $this->pdoToFetchAs($mode);
        }
        assert($mode === NULL || $mode instanceof FetchAs);
        $fetchMode = $mode ?? $this->fetchMode;
        if (isset($columnIndex)) {
            $this->fetchOptions['column'] = $columnIndex;
        }
        if (isset($constructorArguments)) {
            $this->fetchOptions['constructor_args'] = $constructorArguments;
        }
        $return = $this->result
            ->fetchAll($fetchMode, $this->fetchOptions);
        $this->markResultsetFetchingComplete();
        return $return;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchCol($index = 0) {
        return $this->fetchAll(FetchAs::Column, $index);
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAllAssoc($key, $fetch = NULL) {
        if (is_int($fetch)) {
            @trigger_error("Passing the \$fetch argument as an integer to fetchAllAssoc() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use a case of \\Drupal\\Core\\Database\\FetchAs enum instead. See https://www.drupal.org/node/3488338", E_USER_DEPRECATED);
            $fetch = $this->pdoToFetchAs($fetch);
        }
        assert($fetch === NULL || $fetch instanceof FetchAs);
        $result = $this->result
            ->fetchAllAssoc($key, $fetch ?? $this->fetchMode, $this->fetchOptions);
        $this->markResultsetFetchingComplete();
        return $result;
    }
    
    /**
     * {@inheritdoc}
     */
    public function fetchAllKeyed($keyIndex = 0, $valueIndex = 1) {
        $result = $this->result
            ->fetchAllKeyed($keyIndex, $valueIndex);
        $this->markResultsetFetchingComplete();
        return $result;
    }
    
    /**
     * {@inheritdoc}
     */
    public function rowCount() {
        // SELECT query should not use the method.
        if ($this->rowCountEnabled) {
            return $this->result
                ->rowCount();
        }
        else {
            throw new RowCountException();
        }
    }

}

Members

Title Sort descending 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::assocToClass protected function Converts a row of data in FETCH_ASSOC format to FETCH_CLASS.
FetchModeTrait::assocToColumn protected function Converts a row of data in FETCH_ASSOC format to FETCH_COLUMN.
FetchModeTrait::assocToFetchMode protected function Converts a row of data in associative format to a specified format.
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.
PdoTrait::clientExecute protected function Executes the prepared PDO statement.
PdoTrait::clientFetch protected function Fetches the next row from the PDO statement.
PdoTrait::clientFetchAll protected function
PdoTrait::clientFetchColumn protected function Returns a single column from the next row of a result set.
PdoTrait::clientFetchObject protected function Fetches the next row and returns it as an object.
PdoTrait::clientQueryString protected function Returns the query string used to prepare the statement.
PdoTrait::clientRowCount protected function Returns the number of rows affected by the last SQL statement.
PdoTrait::clientSetFetchMode protected function Sets the default fetch mode for the PDO statement.
PdoTrait::fetchAsToPdo protected function Converts a FetchAs mode to a \PDO::FETCH_* constant value.
PdoTrait::getClientStatement public function Returns the client-level database PDO statement object.
PdoTrait::pdoToFetchAs protected function Converts a \PDO::FETCH_* constant value to a FetchAs mode.
StatementBase::$clientStatement protected property The client database Statement object.
StatementBase::$fetchMode protected property Holds the default fetch mode.
StatementBase::$fetchOptions protected property Holds fetch options.
StatementBase::$result protected property The results of a data query language (DQL) statement.
StatementBase::dispatchStatementExecutionEndEvent protected function Dispatches an event informing that the statement execution succeeded.
StatementBase::dispatchStatementExecutionFailureEvent protected function Dispatches an event informing of the statement execution failure.
StatementBase::dispatchStatementExecutionStartEvent protected function Dispatches an event informing that the statement execution begins.
StatementBase::execute abstract public function Executes a prepared statement. Overrides StatementInterface::execute 2
StatementBase::fetch public function Fetches the next row from a result set. Overrides StatementInterface::fetch
StatementBase::fetchAll public function Returns an array containing all of the result set rows. Overrides StatementInterface::fetchAll
StatementBase::fetchAllAssoc public function Returns the result set as an associative array keyed by the given field. Overrides StatementInterface::fetchAllAssoc
StatementBase::fetchAllKeyed public function Returns the entire result set as a single associative array. Overrides StatementInterface::fetchAllKeyed
StatementBase::fetchAssoc public function Fetches the next row and returns it as an associative array. Overrides StatementInterface::fetchAssoc
StatementBase::fetchCol public function Returns an entire single column of a result set as an indexed array. Overrides StatementInterface::fetchCol
StatementBase::fetchField public function Returns a single field from the next record of a result set. Overrides StatementInterface::fetchField
StatementBase::fetchObject public function Fetches the next row and returns it as an object. Overrides StatementInterface::fetchObject
StatementBase::getConnectionTarget public function Returns the target connection this statement is associated with. Overrides StatementInterface::getConnectionTarget
StatementBase::getQueryString public function Gets the query string of this statement. Overrides StatementInterface::getQueryString
StatementBase::rowCount public function Returns the number of rows matched by the last SQL statement. Overrides StatementInterface::rowCount
StatementBase::setFetchMode public function Sets the default fetch mode for this statement. Overrides StatementInterface::setFetchMode
StatementBase::__construct public function Constructor. 2
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.

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