Verified Commit feff7673 authored by Dave Long's avatar Dave Long
Browse files

Issue #3487851 by mondrake, daffie: Replace \PDO::FETCH_* constants to...

Issue #3487851 by mondrake, daffie: Replace \PDO::FETCH_* constants to indicate fetch mode with an enumeration
parent 6dd918c9
Loading
Loading
Loading
Loading
Loading
+12 −6
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
use Drupal\Core\Database\Query\Select;
use Drupal\Core\Database\Query\Truncate;
use Drupal\Core\Database\Query\Update;
use Drupal\Core\Database\Statement\FetchAs;
use Drupal\Core\Database\Transaction\TransactionManagerInterface;
use Drupal\Core\Pager\PagerManagerInterface;

@@ -250,11 +251,10 @@ public function getClientConnection(): object {
   * A given query can be customized with a number of option flags in an
   * associative array:
   * - fetch: This element controls how rows from a result set will be
   *   returned. Legal values include \PDO::FETCH_ASSOC, \PDO::FETCH_BOTH,
   *   \PDO::FETCH_OBJ, \PDO::FETCH_NUM, or a string representing the name of a
   *   class. If a string is specified, each record will be fetched into a new
   *   object of that class. The behavior of all other values is defined by PDO.
   *   See http://php.net/manual/pdostatement.fetch.php
   *   returned. Legal values include one of the enumeration cases of FetchAs or
   *   a string representing the name of a class. If a string is specified, each
   *   record will be fetched into a new object of that class. The behavior of
   *   all other values is described in the FetchAs enum.
   * - allow_delimiter_in_query: By default, queries which have the ; delimiter
   *   any place in them will cause an exception. This reduces the chance of SQL
   *   injection attacks that terminate the original query and add one or more
@@ -277,7 +277,7 @@ public function getClientConnection(): object {
   */
  protected function defaultOptions() {
    return [
      'fetch' => \PDO::FETCH_OBJ,
      'fetch' => FetchAs::Object,
      'allow_delimiter_in_query' => FALSE,
      'allow_square_brackets' => FALSE,
      'pdo' => [],
@@ -432,6 +432,9 @@ public function getFullQualifiedTableName($table) {
   */
  public function prepareStatement(string $query, array $options, bool $allow_row_count = FALSE): StatementInterface {
    assert(!isset($options['return']), 'Passing "return" option to prepareStatement() has no effect. See https://www.drupal.org/node/3185520');
    if (isset($options['fetch']) && is_int($options['fetch'])) {
      @trigger_error("Passing the 'fetch' key as an integer to \$options in prepareStatement() 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);
    }

    try {
      $query = $this->preprocessStatement($query, $options);
@@ -650,6 +653,9 @@ public function query($query, array $args = [], $options = []) {
    assert(is_string($query), 'The \'$query\' argument to ' . __METHOD__ . '() must be a string');
    assert(!isset($options['return']), 'Passing "return" option to query() has no effect. See https://www.drupal.org/node/3185520');
    assert(!isset($options['target']), 'Passing "target" option to query() has no effect. See https://www.drupal.org/node/2993033');
    if (isset($options['fetch']) && is_int($options['fetch'])) {
      @trigger_error("Passing the 'fetch' key as an integer to \$options in query() 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);
    }

    // Use default values if not already set.
    $options += $this->defaultOptions();
+31 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Database\Statement;

/**
 * Enumeration of the fetch modes for result sets.
 */
enum FetchAs {

  // Returns an anonymous object with property names that correspond to the
  // column names returned in the result set. This is the default fetch mode
  // for Drupal.
  case Object;

  // Returns a new instance of a requested class, mapping the columns of the
  // result set to named properties in the class.
  case ClassObject;

  // Returns an array indexed by column name as returned in the result set.
  case Associative;

  // Returns an array indexed by column number as returned in the result set,
  // starting at column 0.
  case List;

  // Returns a single column from the next row of a result set.
  case Column;

}
+229 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Database\Statement;

/**
 * A trait for calling \PDOStatement methods.
 */
trait PdoTrait {

  /**
   * Converts a FetchAs mode to a \PDO::FETCH_* constant value.
   *
   * @param \Drupal\Core\Database\FetchAs $mode
   *   The FetchAs mode.
   *
   * @return int
   *   A \PDO::FETCH_* constant value.
   */
  protected function fetchAsToPdo(FetchAs $mode): int {
    return match ($mode) {
      FetchAs::Associative => \PDO::FETCH_ASSOC,
      FetchAs::ClassObject => \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE,
      FetchAs::Column => \PDO::FETCH_COLUMN,
      FetchAs::List => \PDO::FETCH_NUM,
      FetchAs::Object => \PDO::FETCH_OBJ,
    };
  }

  /**
   * Converts a \PDO::FETCH_* constant value to a FetchAs mode.
   *
   * @param int $mode
   *   The \PDO::FETCH_* constant value.
   *
   * @return \Drupal\Core\Database\FetchAs
   *   A FetchAs mode.
   */
  protected function pdoToFetchAs(int $mode): FetchAs {
    return match ($mode) {
      \PDO::FETCH_ASSOC => FetchAs::Associative,
      \PDO::FETCH_CLASS, \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE => FetchAs::ClassObject,
      \PDO::FETCH_COLUMN => FetchAs::Column,
      \PDO::FETCH_NUM => FetchAs::List,
      \PDO::FETCH_OBJ => FetchAs::Object,
      default => throw new \RuntimeException('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'),
    };
  }

  /**
   * Returns the client-level database PDO statement object.
   *
   * This method should normally be used only within database driver code.
   *
   * @return \PDOStatement
   *   The client-level database PDO statement.
   *
   * @throws \RuntimeException
   *   If the client-level statement is not set.
   */
  public function getClientStatement(): \PDOStatement {
    if (isset($this->clientStatement)) {
      assert($this->clientStatement instanceof \PDOStatement);
      return $this->clientStatement;
    }
    throw new \LogicException('\\PDOStatement not initialized');
  }

  /**
   * Sets the default fetch mode for the PDO statement.
   *
   * @param \Drupal\Core\Database\FetchAs $mode
   *   One of the cases of the FetchAs enum.
   * @param int|class-string|null $columnOrClass
   *   If $mode is FetchAs::Column, the index of the column to fetch.
   *   If $mode is FetchAs::ClassObject, the FQCN of the object.
   * @param list<mixed>|null $constructorArguments
   *   If $mode is FetchAs::ClassObject, the arguments to pass to the
   *   constructor.
   *
   * @return bool
   *   Returns true on success or false on failure.
   */
  protected function clientSetFetchMode(FetchAs $mode, int|string|null $columnOrClass = NULL, array|null $constructorArguments = NULL): bool {
    return match ($mode) {
      FetchAs::Column => $this->getClientStatement()->setFetchMode(
        \PDO::FETCH_COLUMN,
        $columnOrClass ?? $this->fetchOptions['column'],
      ),
      FetchAs::ClassObject => $this->getClientStatement()->setFetchMode(
        \PDO::FETCH_CLASS,
        $columnOrClass ?? $this->fetchOptions['class'],
        $constructorArguments ?? $this->fetchOptions['constructor_args'],
      ),
      default => $this->getClientStatement()->setFetchMode(
        $this->fetchAsToPdo($mode),
      ),
    };
  }

  /**
   * Executes the prepared PDO statement.
   *
   * @param array|null $arguments
   *   An array of values with as many elements as there are bound parameters in
   *   the SQL statement being executed. This can be NULL.
   * @param array $options
   *   An array of options for this query.
   *
   * @return bool
   *   TRUE on success, or FALSE on failure.
   */
  protected function clientExecute(?array $arguments = [], array $options = []): bool {
    return $this->getClientStatement()->execute($arguments);
  }

  /**
   * Fetches the next row from the PDO statement.
   *
   * @param \Drupal\Core\Database\FetchAs|null $mode
   *   (Optional) one of the cases of the FetchAs enum. If not specified,
   *   defaults to what is specified by setFetchMode().
   * @param int|null $cursorOrientation
   *   Not implemented in all database drivers, don't use.
   * @param int|null $cursorOffset
   *   Not implemented in all database drivers, don't use.
   *
   * @return array<scalar|null>|object|scalar|null|false
   *   A result, formatted according to $mode, or FALSE on failure.
   */
  protected function clientFetch(?FetchAs $mode = NULL, ?int $cursorOrientation = NULL, ?int $cursorOffset = NULL): array|object|int|float|string|bool|NULL {
    return match(func_num_args()) {
      0 => $this->getClientStatement()->fetch(),
      1 => $this->getClientStatement()->fetch($this->fetchAsToPdo($mode)),
      2 => $this->getClientStatement()->fetch($this->fetchAsToPdo($mode), $cursorOrientation),
      default => $this->getClientStatement()->fetch($this->fetchAsToPdo($mode), $cursorOrientation, $cursorOffset),
    };
  }

  /**
   * Returns a single column from the next row of a result set.
   *
   * @param int $column
   *   0-indexed number of the column to retrieve from the row. If no value is
   *   supplied, the first column is fetched.
   *
   * @return scalar|null|false
   *   A single column from the next row of a result set or false if there are
   *   no more rows.
   */
  protected function clientFetchColumn(int $column = 0): int|float|string|bool|NULL {
    return $this->getClientStatement()->fetchColumn($column);
  }

  /**
   * Fetches the next row and returns it as an object.
   *
   * @param class-string|null $class
   *   FQCN of the class to be instantiated.
   * @param list<mixed>|null $constructorArguments
   *   The arguments to be passed to the constructor.
   *
   * @return object|false
   *   An instance of the required class with property names that correspond
   *   to the column names, or FALSE on failure.
   */
  protected function clientFetchObject(?string $class = NULL, array $constructorArguments = []): object|FALSE {
    if ($class) {
      return $this->getClientStatement()->fetchObject($class, $constructorArguments);
    }
    return $this->getClientStatement()->fetchObject();
  }

  /**
   * Returns an array containing all of the result set rows.
   *
   * @param \Drupal\Core\Database\FetchAs|null $mode
   *   (Optional) one of the cases of the FetchAs enum. If not specified,
   *   defaults to what is specified by setFetchMode().
   * @param int|class-string|null $columnOrClass
   *   If $mode is FetchAs::Column, the index of the column to fetch.
   *   If $mode is FetchAs::ClassObject, the FQCN of the object.
   * @param list<mixed>|null $constructorArguments
   *   If $mode is FetchAs::ClassObject, the arguments to pass to the
   *   constructor.
   *
   * @return array<array<scalar|null>|object|scalar|null>
   *   An array of results.
   */
  // phpcs:ignore Drupal.Commenting.FunctionComment.InvalidReturn, Drupal.Commenting.FunctionComment.Missing
  protected function clientFetchAll(?FetchAs $mode = NULL, int|string|null $columnOrClass = NULL, array|null $constructorArguments = NULL): array {
    return match ($mode) {
      FetchAs::Column => $this->getClientStatement()->fetchAll(
        \PDO::FETCH_COLUMN,
        $columnOrClass ?? $this->fetchOptions['column'],
      ),
      FetchAs::ClassObject => $this->getClientStatement()->fetchAll(
        \PDO::FETCH_CLASS,
        $columnOrClass ?? $this->fetchOptions['class'],
        $constructorArguments ?? $this->fetchOptions['constructor_args'],
      ),
      default => $this->getClientStatement()->fetchAll(
        $this->fetchAsToPdo($mode ?? $this->defaultFetchMode),
      ),
    };
  }

  /**
   * Returns the number of rows affected by the last SQL statement.
   *
   * @return int
   *   The number of rows.
   */
  protected function clientRowCount(): int {
    return $this->getClientStatement()->rowCount();
  }

  /**
   * Returns the query string used to prepare the statement.
   *
   * @return string
   *   The query string.
   */
  protected function clientQueryString(): string {
    return $this->getClientStatement()->queryString;
  }

}
+24 −26
Original line number Diff line number Diff line
@@ -65,18 +65,15 @@ public function rowCount();
  /**
   * Sets the default fetch mode for this statement.
   *
   * See http://php.net/manual/pdo.constants.php for the definition of the
   * constants used.
   *
   * @param int $mode
   *   One of the \PDO::FETCH_* constants.
   * @param int|null $a1
   * @param \Drupal\Core\Database\FetchAs|int $mode
   *   One of the cases of the FetchAs enum, or (deprecated) a \PDO::FETCH_*
   *   constant.
   * @param string|int|null $a1
   *   An option depending of the fetch mode specified by $mode:
   *   - for \PDO::FETCH_COLUMN, the index of the column to fetch
   *   - for \PDO::FETCH_CLASS, the name of the class to create
   *   - for \PDO::FETCH_INTO, the object to add the data to
   * @param array $a2
   *   If $mode is \PDO::FETCH_CLASS, the optional arguments to pass to the
   *   - for FetchAs::Column, the index of the column to fetch;
   *   - for FetchAs::ClassObject, the name of the class to create.
   * @param list<mixed> $a2
   *   If $mode is FetchAs::ClassObject, the optional arguments to pass to the
   *   constructor.
   */
  public function setFetchMode($mode, $a1 = NULL, $a2 = []);
@@ -84,12 +81,10 @@ public function setFetchMode($mode, $a1 = NULL, $a2 = []);
  /**
   * Fetches the next row from a result set.
   *
   * See http://php.net/manual/pdo.constants.php for the definition of the
   * constants used.
   *
   * @param int $mode
   *   One of the \PDO::FETCH_* constants.
   *   Default to what was specified by setFetchMode().
   * @param \Drupal\Core\Database\FetchAs|int|null $mode
   *   (Optional) one of the cases of the FetchAs enum, or (deprecated) a
   *   \PDO::FETCH_* constant. If not specified, defaults to what is specified
   *   by setFetchMode().
   * @param int|null $cursor_orientation
   *   Not implemented in all database drivers, don't use.
   * @param int|null $cursor_offset
@@ -146,12 +141,14 @@ public function fetchAssoc();
  /**
   * Returns an array containing all of the result set rows.
   *
   * @param int|null $mode
   *   One of the \PDO::FETCH_* constants.
   * @param \Drupal\Core\Database\FetchAs|int|null $mode
   *   (Optional) one of the cases of the FetchAs enum, or (deprecated) a
   *   \PDO::FETCH_* constant. If not specified, defaults to what is specified
   *   by setFetchMode().
   * @param int|null $column_index
   *   If $mode is \PDO::FETCH_COLUMN, the index of the column to fetch.
   *   If $mode is FetchAs::Column, the index of the column to fetch.
   * @param array $constructor_arguments
   *   If $mode is \PDO::FETCH_CLASS, the arguments to pass to the constructor.
   *   If $mode is FetchAs::ClassObject, the arguments to pass to the constructor.
   *
   * @return array
   *   An array of results.
@@ -202,11 +199,12 @@ public function fetchAllKeyed($key_index = 0, $value_index = 1);
   *
   * @param string $key
   *   The name of the field on which to index the array.
   * @param int|null $fetch
   *   The fetch mode to use. If set to \PDO::FETCH_ASSOC, \PDO::FETCH_NUM, or
   *   \PDO::FETCH_BOTH the returned value with be an array of arrays. For any
   *   other value it will be an array of objects. By default, the fetch mode
   *   set for the query will be used.
   * @param \Drupal\Core\Database\FetchAs|int|string|null $fetch
   *   (Optional) the fetch mode to use. One of the cases of the FetchAs enum,
   *   or (deprecated) a \PDO::FETCH_* constant. If set to FetchAs::Associative
   *   or FetchAs::List the returned value with be an array of arrays. For any
   *   other value it will be an array of objects. If not specified, defaults to
   *   what is specified by setFetchMode().
   *
   * @return array
   *   An associative array, or an empty array if there is no result set.
+71 −35

File changed.

Preview size limit exceeded, changes collapsed.

Loading