StatementBase.php

Namespace

Drupal\Core\Database\Statement

File

core/lib/Drupal/Core/Database/Statement/StatementBase.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Core\Database\Statement;

use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Event\StatementExecutionEndEvent;
use Drupal\Core\Database\Event\StatementExecutionFailureEvent;
use Drupal\Core\Database\Event\StatementExecutionStartEvent;
use Drupal\Core\Database\FetchModeTrait;
use Drupal\Core\Database\RowCountException;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Database\StatementIteratorTrait;

/**
 * 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.
 */
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();
        }
    }

}

Classes

Title Deprecated Summary
StatementBase StatementInterface base implementation.

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