diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index f28f60a521026bce85946f3d866c7de6fb1861d7..60991cbbdb6df32f1d7203b88e11a6b6663df29d 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -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(); diff --git a/core/lib/Drupal/Core/Database/Statement/FetchAs.php b/core/lib/Drupal/Core/Database/Statement/FetchAs.php new file mode 100644 index 0000000000000000000000000000000000000000..051ed412a2b1c1bae4382c7a75ef3d6d018b6235 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Statement/FetchAs.php @@ -0,0 +1,31 @@ +<?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; + +} diff --git a/core/lib/Drupal/Core/Database/Statement/PdoTrait.php b/core/lib/Drupal/Core/Database/Statement/PdoTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..064c882b7f11c7f665148874f4c0fe46358d9369 --- /dev/null +++ b/core/lib/Drupal/Core/Database/Statement/PdoTrait.php @@ -0,0 +1,229 @@ +<?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; + } + +} diff --git a/core/lib/Drupal/Core/Database/StatementInterface.php b/core/lib/Drupal/Core/Database/StatementInterface.php index c7d19201ffa685de4abe3ce511b210645a6abe0f..afef8ea9932dd4a5bafa7825af9d42851a59c2a1 100644 --- a/core/lib/Drupal/Core/Database/StatementInterface.php +++ b/core/lib/Drupal/Core/Database/StatementInterface.php @@ -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. diff --git a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php index 8d6a1b3906dc8ddd89914bba35a58f9274df3b5f..70209b2d12e06a48db2db1361d0060a10add065e 100644 --- a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php +++ b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php @@ -5,6 +5,8 @@ use Drupal\Core\Database\Event\StatementExecutionEndEvent; use Drupal\Core\Database\Event\StatementExecutionFailureEvent; use Drupal\Core\Database\Event\StatementExecutionStartEvent; +use Drupal\Core\Database\Statement\FetchAs; +use Drupal\Core\Database\Statement\PdoTrait; /** * An implementation of StatementInterface that prefetches all data. @@ -15,13 +17,21 @@ */ class StatementPrefetchIterator implements \Iterator, StatementInterface { - use StatementIteratorTrait; 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; /** * Main data store. * - * The resultset is stored as a \PDO::FETCH_ASSOC array. + * The resultset is stored as a FetchAs::Associative array. */ protected array $data = []; @@ -39,18 +49,27 @@ class StatementPrefetchIterator implements \Iterator, StatementInterface { /** * Holds the default fetch style. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * $defaultFetchMode instead. + * + * @see https://www.drupal.org/node/3488338 */ protected int $defaultFetchStyle = \PDO::FETCH_OBJ; + /** + * Holds the default fetch mode. + */ + protected FetchAs $defaultFetchMode = FetchAs::Object; + /** * Holds fetch options. * - * @var string[] + * @var array{'class': class-string, 'constructor_args': array<mixed>, 'column': int} */ protected array $fetchOptions = [ 'class' => 'stdClass', 'constructor_args' => [], - 'object' => NULL, 'column' => 0, ]; @@ -88,12 +107,16 @@ public function getConnectionTarget(): string { * {@inheritdoc} */ public function execute($args = [], $options = []) { + if (isset($options['fetch']) && is_int($options['fetch'])) { + @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() 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); + } + 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']); + $this->setFetchMode(FetchAs::ClassObject, $options['fetch']); } else { $this->setFetchMode($options['fetch']); @@ -114,8 +137,8 @@ public function execute($args = [], $options = []) { // Prepare and execute the statement. try { - $statement = $this->getStatement($this->queryString, $args); - $return = $statement->execute($args); + $this->clientStatement = $this->getStatement($this->queryString, $args); + $return = $this->clientExecute($args, $options); } catch (\Exception $e) { if (isset($startEvent) && $this->connection->isEventEnabled(StatementExecutionFailureEvent::class)) { @@ -132,16 +155,17 @@ public function execute($args = [], $options = []) { $e->getMessage(), )); } + unset($this->clientStatement); 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; + $this->data = $this->clientFetchAll(FetchAs::Associative); + $this->rowCount = $this->rowCountEnabled ? $this->clientRowCount() : NULL; // Destroy the statement as soon as possible. See the documentation of // \Drupal\sqlite\Driver\Database\sqlite\Statement for an explanation. - unset($statement); + unset($this->clientStatement); $this->markResultsetIterable($return); $this->columnNames = count($this->data) > 0 ? array_keys($this->data[0]) : []; @@ -190,24 +214,27 @@ public function getQueryString() { * {@inheritdoc} */ public function setFetchMode($mode, $a1 = NULL, $a2 = []) { - assert(in_array($mode, $this->supportedFetchModes), 'Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'); + 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); + } - $this->defaultFetchStyle = $mode; + $this->defaultFetchMode = $mode; + // @todo Remove backwards compatibility statement below in drupal:12.0.0. + // @phpstan-ignore property.deprecated + $this->defaultFetchStyle = $this->fetchAsToPdo($mode); switch ($mode) { - case \PDO::FETCH_CLASS: + case FetchAs::ClassObject: $this->fetchOptions['class'] = $a1; if ($a2) { $this->fetchOptions['constructor_args'] = $a2; } break; - case \PDO::FETCH_COLUMN: + case FetchAs::Column: $this->fetchOptions['column'] = $a1; break; - case \PDO::FETCH_INTO: - $this->fetchOptions['object'] = $a1; - break; } } @@ -228,6 +255,11 @@ public function rowCount() { * {@inheritdoc} */ public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) { + if (is_int($fetch_style)) { + @trigger_error("Passing the \$fetch_style 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); + $fetch_style = $this->pdoToFetchAs($fetch_style); + } + $currentKey = $this->getResultsetCurrentRowIndex(); // We can remove the current record from the prefetched data, before @@ -242,14 +274,13 @@ public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI // Now, format the next prefetched record according to the required fetch // style. $rowAssoc = $this->data[$currentKey]; - $mode = $fetch_style ?? $this->defaultFetchStyle; + $mode = $fetch_style ?? $this->defaultFetchMode; $row = match($mode) { - \PDO::FETCH_ASSOC => $rowAssoc, - \PDO::FETCH_CLASS, \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE => $this->assocToClass($rowAssoc, $this->fetchOptions['class'], $this->fetchOptions['constructor_args']), - \PDO::FETCH_COLUMN => $this->assocToColumn($rowAssoc, $this->columnNames, $this->fetchOptions['column']), - \PDO::FETCH_NUM => $this->assocToNum($rowAssoc), - \PDO::FETCH_OBJ => $this->assocToObj($rowAssoc), - default => throw new DatabaseExceptionWrapper('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'), + FetchAs::Associative => $rowAssoc, + FetchAs::ClassObject => $this->assocToClass($rowAssoc, $this->fetchOptions['class'], $this->fetchOptions['constructor_args']), + FetchAs::Column => $this->assocToColumn($rowAssoc, $this->columnNames, $this->fetchOptions['column']), + FetchAs::List => $this->assocToNum($rowAssoc), + FetchAs::Object => $this->assocToObj($rowAssoc), }; $this->setResultsetCurrentRow($row); return $row; @@ -270,7 +301,7 @@ public function fetchColumn($index = 0) { * {@inheritdoc} */ public function fetchField($index = 0) { - if ($row = $this->fetch(\PDO::FETCH_ASSOC)) { + if ($row = $this->fetch(FetchAs::Associative)) { return $this->assocToColumn($row, $this->columnNames, $index); } return FALSE; @@ -281,30 +312,32 @@ public function fetchField($index = 0) { */ public function fetchObject(?string $class_name = NULL, array $constructor_arguments = []) { if (!isset($class_name)) { - return $this->fetch(\PDO::FETCH_OBJ); + return $this->fetch(FetchAs::Object); } $this->fetchOptions = [ 'class' => $class_name, 'constructor_args' => $constructor_arguments, ]; - return $this->fetch(\PDO::FETCH_CLASS); + return $this->fetch(FetchAs::ClassObject); } /** * {@inheritdoc} */ public function fetchAssoc() { - return $this->fetch(\PDO::FETCH_ASSOC); + return $this->fetch(FetchAs::Associative); } /** * {@inheritdoc} */ public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) { - $fetchStyle = $mode ?? $this->defaultFetchStyle; - - assert(in_array($fetchStyle, $this->supportedFetchModes), 'Fetch mode ' . ($this->fetchModeLiterals[$fetchStyle] ?? $fetchStyle) . ' is not supported. Use supported modes only.'); + 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); + } + $fetchStyle = $mode ?? $this->defaultFetchMode; if (isset($column_index)) { $this->fetchOptions['column'] = $column_index; } @@ -342,7 +375,7 @@ public function fetchAllKeyed($key_index = 0, $value_index = 1) { $value = $this->columnNames[$value_index]; $result = []; - while ($row = $this->fetch(\PDO::FETCH_ASSOC)) { + while ($row = $this->fetch(FetchAs::Associative)) { $result[$row[$key]] = $row[$value]; } return $result; @@ -351,11 +384,14 @@ public function fetchAllKeyed($key_index = 0, $value_index = 1) { /** * {@inheritdoc} */ - public function fetchAllAssoc($key, $fetch_style = NULL) { - $fetchStyle = $fetch_style ?? $this->defaultFetchStyle; + 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); + } $result = []; - while ($row = $this->fetch($fetchStyle)) { + while ($row = $this->fetch($fetch ?? $this->defaultFetchMode)) { $result[$this->data[$this->getResultsetCurrentRowIndex()][$key]] = $row; } return $result; diff --git a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php index 980ef74ed2e103575fd361ce00c92baa8f3dc0b5..364cbe05b4159f58b8f191a7b99f9030f40f9cfd 100644 --- a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php +++ b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php @@ -5,6 +5,8 @@ use Drupal\Core\Database\Event\StatementExecutionEndEvent; use Drupal\Core\Database\Event\StatementExecutionFailureEvent; use Drupal\Core\Database\Event\StatementExecutionStartEvent; +use Drupal\Core\Database\Statement\FetchAs; +use Drupal\Core\Database\Statement\PdoTrait; // cSpell:ignore maxlen driverdata INOUT @@ -28,8 +30,9 @@ */ class StatementWrapperIterator implements \Iterator, StatementInterface { - use StatementIteratorTrait; use FetchModeTrait; + use PdoTrait; + use StatementIteratorTrait; /** * The client database Statement object. @@ -38,6 +41,22 @@ class StatementWrapperIterator implements \Iterator, StatementInterface { */ protected object $clientStatement; + /** + * Holds the default fetch mode. + */ + protected FetchAs $defaultFetchMode = FetchAs::Object; + + /** + * Holds fetch options. + * + * @var array{'class': class-string, 'constructor_args': array<mixed>, 'column': int} + */ + protected array $fetchOptions = [ + 'class' => 'stdClass', + 'constructor_args' => [], + 'column' => 0, + ]; + /** * Constructs a StatementWrapperIterator object. * @@ -60,19 +79,7 @@ public function __construct( protected readonly bool $rowCountEnabled = FALSE, ) { $this->clientStatement = $clientConnection->prepare($query, $options); - $this->setFetchMode(\PDO::FETCH_OBJ); - } - - /** - * Returns the client-level database statement object. - * - * This method should normally be used only within database driver code. - * - * @return object - * The client-level database statement, for example \PDOStatement. - */ - public function getClientStatement(): object { - return $this->clientStatement; + $this->setFetchMode(FetchAs::Object); } /** @@ -86,11 +93,13 @@ public function getConnectionTarget(): string { * {@inheritdoc} */ public function execute($args = [], $options = []) { + if (isset($options['fetch']) && is_int($options['fetch'])) { + @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() 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); + } + if (isset($options['fetch'])) { if (is_string($options['fetch'])) { - // \PDO::FETCH_PROPS_LATE tells __construct() to run before properties - // are added to the object. - $this->setFetchMode(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, $options['fetch']); + $this->setFetchMode(FetchAs::ClassObject, $options['fetch']); } else { $this->setFetchMode($options['fetch']); @@ -110,7 +119,7 @@ public function execute($args = [], $options = []) { } try { - $return = $this->clientStatement->execute($args); + $return = $this->clientExecute($args, $options); $this->markResultsetIterable($return); } catch (\Exception $e) { @@ -150,23 +159,28 @@ public function execute($args = [], $options = []) { * {@inheritdoc} */ public function getQueryString() { - return $this->clientStatement->queryString; + return $this->clientQueryString(); } /** * {@inheritdoc} */ public function fetchCol($index = 0) { - return $this->fetchAll(\PDO::FETCH_COLUMN, $index); + 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); + } + if (isset($fetch)) { if (is_string($fetch)) { - $this->setFetchMode(\PDO::FETCH_CLASS, $fetch); + $this->setFetchMode(FetchAs::ClassObject, $fetch); } else { $this->setFetchMode($fetch); @@ -193,7 +207,7 @@ public function fetchAllAssoc($key, $fetch = NULL) { * {@inheritdoc} */ public function fetchAllKeyed($key_index = 0, $value_index = 1) { - $this->setFetchMode(\PDO::FETCH_NUM); + $this->setFetchMode(FetchAs::List); // Return early if the statement was already fully traversed. if (!$this->isResultsetIterable) { @@ -213,8 +227,7 @@ public function fetchAllKeyed($key_index = 0, $value_index = 1) { * {@inheritdoc} */ public function fetchField($index = 0) { - // Call \PDOStatement::fetchColumn to fetch the field. - $column = $this->clientStatement->fetchColumn($index); + $column = $this->clientFetchColumn($index); if ($column === FALSE) { $this->markResultsetFetchingComplete(); @@ -229,19 +242,14 @@ public function fetchField($index = 0) { * {@inheritdoc} */ public function fetchAssoc() { - return $this->fetch(\PDO::FETCH_ASSOC); + return $this->fetch(FetchAs::Associative); } /** * {@inheritdoc} */ public function fetchObject(?string $class_name = NULL, array $constructor_arguments = []) { - if ($class_name) { - $row = $this->clientStatement->fetchObject($class_name, $constructor_arguments); - } - else { - $row = $this->clientStatement->fetchObject(); - } + $row = $this->clientFetchObject($class_name, $constructor_arguments); if ($row === FALSE) { $this->markResultsetFetchingComplete(); @@ -258,7 +266,7 @@ public function fetchObject(?string $class_name = NULL, array $constructor_argum public function rowCount() { // SELECT query should not use the method. if ($this->rowCountEnabled) { - return $this->clientStatement->rowCount(); + return $this->clientRowCount(); } else { throw new RowCountException(); @@ -269,32 +277,43 @@ public function rowCount() { * {@inheritdoc} */ public function setFetchMode($mode, $a1 = NULL, $a2 = []) { - assert(in_array($mode, $this->supportedFetchModes), 'Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'); - - // Call \PDOStatement::setFetchMode to set fetch mode. - // \PDOStatement is picky about the number of arguments in some cases so we - // need to be pass the exact number of arguments we where given. - return match(func_num_args()) { - 1 => $this->clientStatement->setFetchMode($mode), - 2 => $this->clientStatement->setFetchMode($mode, $a1), - default => $this->clientStatement->setFetchMode($mode, $a1, $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); + } + + $this->defaultFetchMode = $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; + + } + + return $this->clientSetFetchMode($mode, $a1, $a2); } /** * {@inheritdoc} */ public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL) { - assert(!isset($mode) || in_array($mode, $this->supportedFetchModes), 'Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'); + 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); + } - // Call \PDOStatement::fetchAll to fetch all rows. - // \PDOStatement is picky about the number of arguments in some cases so we - // need to pass the exact number of arguments we were given. $row = match(func_num_args()) { - 0 => $this->clientStatement->fetch(), - 1 => $this->clientStatement->fetch($mode), - 2 => $this->clientStatement->fetch($mode, $cursor_orientation), - default => $this->clientStatement->fetch($mode, $cursor_orientation, $cursor_offset), + 0 => $this->clientFetch(), + 1 => $this->clientFetch($mode), + 2 => $this->clientFetch($mode, $cursor_orientation), + default => $this->clientFetch($mode, $cursor_orientation, $cursor_offset), }; if ($row === FALSE) { @@ -310,17 +329,20 @@ public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = * {@inheritdoc} */ public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) { - assert(!isset($mode) || in_array($mode, $this->supportedFetchModes), 'Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is not supported. Use supported modes only.'); - - // Call \PDOStatement::fetchAll to fetch all rows. - // \PDOStatement is picky about the number of arguments in some cases so we - // need to be pass the exact number of arguments we where given. - $return = match(func_num_args()) { - 0 => $this->clientStatement->fetchAll(), - 1 => $this->clientStatement->fetchAll($mode), - 2 => $this->clientStatement->fetchAll($mode, $column_index), - default => $this->clientStatement->fetchAll($mode, $column_index, $constructor_arguments), - }; + 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); + } + + $fetchMode = $mode ?? $this->defaultFetchMode; + if (isset($column_index)) { + $this->fetchOptions['column'] = $column_index; + } + if (isset($constructor_arguments)) { + $this->fetchOptions['constructor_args'] = $constructor_arguments; + } + + $return = $this->clientFetchAll($fetchMode, $column_index, $constructor_arguments); $this->markResultsetFetchingComplete(); diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index eeb714f6ad48434d5e2e25043a177c4ab103b8d3..78d6061100f66644fa3da068dad6b566f4bfb79f 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -7,6 +7,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Database\SchemaException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityStorageBase; use Drupal\Core\Entity\ContentEntityTypeInterface; @@ -530,7 +531,7 @@ protected function loadFromSharedTables(array &$values, array &$translations, $l // latest revision. Otherwise we fall back to the data table. $table = $this->revisionDataTable ?: $this->dataTable; $alias = $this->revisionDataTable ? 'revision' : 'data'; - $query = $this->database->select($table, $alias, ['fetch' => \PDO::FETCH_ASSOC]) + $query = $this->database->select($table, $alias, ['fetch' => FetchAs::Associative]) ->fields($alias) ->condition($alias . '.' . $record_key, array_keys($values), 'IN') ->orderBy($alias . '.' . $record_key); @@ -1641,7 +1642,7 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted()); // Get the entities which we want to purge first. - $entity_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]); + $entity_query = $this->database->select($table_name, 't', ['fetch' => FetchAs::Associative]); $or = $entity_query->orConditionGroup(); foreach ($storage_definition->getColumns() as $column_name => $data) { $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name)); @@ -1661,7 +1662,7 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit $entities = []; $items_by_entity = []; foreach ($entity_query->execute() as $row) { - $item_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]) + $item_query = $this->database->select($table_name, 't', ['fetch' => FetchAs::Associative]) ->fields('t') ->condition('entity_id', $row['entity_id']) ->condition('deleted', 1) diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php index 2dc2fd53c6f1def085a5a528d97f0c94c2cc2783..fb5d75a9c2dd46bcaee7f349babd786f2143bd6b 100644 --- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php +++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php @@ -10,6 +10,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\DatabaseException; use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Database\Statement\FetchAs; // cspell:ignore mlid @@ -642,7 +643,7 @@ public function loadByProperties(array $properties) { } $query->condition($name, $value); } - $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC); + $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); foreach ($loaded as $id => $link) { $loaded[$id] = $this->prepareLink($link); } @@ -671,7 +672,7 @@ public function loadByRoute($route_name, array $route_parameters = [], $menu_nam $query->orderBy('depth'); $query->orderBy('weight'); $query->orderBy('id'); - $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC); + $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); foreach ($loaded as $id => $link) { $loaded[$id] = $this->prepareLink($link); } @@ -688,7 +689,7 @@ public function loadMultiple(array $ids) { $query = $this->connection->select($this->table, NULL, $this->options); $query->fields($this->table, $this->definitionFields()); $query->condition('id', $missing_ids, 'IN'); - $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC); + $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); foreach ($loaded as $id => $link) { $this->definitions[$id] = $this->prepareLink($link); } @@ -734,7 +735,7 @@ protected function loadFullMultiple(array $ids) { $query = $this->connection->select($this->table, NULL, $this->options); $query->fields($this->table); $query->condition('id', $ids, 'IN'); - $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC); + $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); foreach ($loaded as &$link) { foreach ($this->serializedFields() as $name) { if (isset($link[$name])) { @@ -755,7 +756,7 @@ public function getRootPathIds($id) { // https://www.drupal.org/node/2302043 $subquery->fields($this->table, ['p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9']); $subquery->condition('id', $id); - $result = current($subquery->execute()->fetchAll(\PDO::FETCH_ASSOC)); + $result = current($subquery->execute()->fetchAll(FetchAs::Associative)); $ids = array_filter($result); if ($ids) { $query = $this->connection->select($this->table, NULL, $this->options); @@ -942,7 +943,7 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) { } } - $links = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC); + $links = $this->safeExecuteSelect($query)->fetchAllAssoc('id', FetchAs::Associative); return $links; } diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php index ef456eca5195824c211126330af9c4d2efe7a620..3330303114ce2037735755a01b0b43085f668e4d 100644 --- a/core/lib/Drupal/Core/Routing/RouteProvider.php +++ b/core/lib/Drupal/Core/Routing/RouteProvider.php @@ -5,6 +5,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Path\CurrentPathStack; @@ -383,7 +384,7 @@ protected function getRoutesByPath($path) { ':patterns[]' => $ancestors, ':count_parts' => count($parts), ]) - ->fetchAll(\PDO::FETCH_ASSOC); + ->fetchAll(FetchAs::Associative); } catch (\Exception) { $routes = []; diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 46775c5de0f5b803c1388fcc30ff2ac0646ed4cf..99710776cdd68e43cc0f679f1d648f4768b72499 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -6,6 +6,7 @@ use Drupal\Core\Database\DatabaseException; use Drupal\Core\Database\DatabaseExceptionWrapper; use Drupal\Core\Database\Exception\SchemaTableKeyTooLargeException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginBase; @@ -655,7 +656,7 @@ public function lookupDestinationIds(array $source_id_values) { } try { - return $query->execute()->fetchAll(\PDO::FETCH_NUM); + return $query->execute()->fetchAll(FetchAs::List); } catch (DatabaseExceptionWrapper) { // It's possible that the query will cause an exception to be thrown. For diff --git a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php index 05123fe9b780502620401a075d3210aa231fe6bc..40dea1c7a2b362c597f00070659baab189836734 100644 --- a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php +++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\ConnectionNotDefinedException; use Drupal\Core\Database\Database; use Drupal\Core\Database\DatabaseException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Exception\RequirementsException; @@ -224,7 +225,7 @@ public function checkRequirements() { * Wrapper for database select. */ protected function select($table, $alias = NULL, array $options = []) { - $options['fetch'] = \PDO::FETCH_ASSOC; + $options['fetch'] = FetchAs::Associative; return $this->getDatabase()->select($table, $alias, $options); } @@ -347,7 +348,7 @@ protected function initializeIterator() { $this->query->range($this->batch * $this->batchSize, $this->batchSize); } $statement = $this->query->execute(); - $statement->setFetchMode(\PDO::FETCH_ASSOC); + $statement->setFetchMode(FetchAs::Associative); return new \IteratorIterator($statement); } diff --git a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php index 20477876e9988c5c1abbb4ef64698a51be6e9114..2f5c2a2ceec76a49f482d633855086d4d6ab3022 100644 --- a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php +++ b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php @@ -6,6 +6,7 @@ use Drupal\Core\Database\Query\ConditionInterface; use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Database\StatementInterface; use Drupal\migrate\Exception\RequirementsException; use Drupal\Core\Database\Database; @@ -175,7 +176,7 @@ public function testHighWater($high_water = NULL, array $query_result = []): voi } $statement = $this->createMock(StatementInterface::class); - $statement->expects($this->atLeastOnce())->method('setFetchMode')->with(\PDO::FETCH_ASSOC); + $statement->expects($this->atLeastOnce())->method('setFetchMode')->with(FetchAs::Associative); $query = $this->createMock(SelectInterface::class); $query->method('execute')->willReturn($statement); $query->expects($this->atLeastOnce())->method('orderBy')->with('order', 'ASC'); diff --git a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php index ce25ea4d21f20f30f4354465934a5383b94f7737..cd64f935ca1e6e6ec9309a3ebba006cd569f651d 100644 --- a/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php +++ b/core/modules/migrate_drupal/src/MigrationConfigurationTrait.php @@ -5,6 +5,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\Database; use Drupal\Core\Database\DatabaseExceptionWrapper; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\RequirementsInterface; @@ -70,7 +71,7 @@ protected function getSystemData(Connection $connection) { $system_data = []; try { $results = $connection->select('system', 's', [ - 'fetch' => \PDO::FETCH_ASSOC, + 'fetch' => FetchAs::Associative, ]) ->fields('s') ->execute(); diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php index 90dcb8c92a4a1361fbee4b0cec3c36cec22d9565..8f30abbc564208b886f9a86de1e33b7d9ce8f4f5 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\node\Kernel\Migrate\d6; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\node\NodeInterface; use Drupal\Tests\file\Kernel\Migrate\d6\FileMigrationTestTrait; use Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait; @@ -65,14 +66,14 @@ public function testNodeCompleteMigration(): void { ->orderBy('vid') ->orderBy('langcode') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC)); + ->fetchAll(FetchAs::Associative)); $this->assertEquals($this->expectedNodeFieldDataTable(), $db->select('node_field_data', 'nr') ->fields('nr') ->orderBy('nid') ->orderBy('vid') ->orderBy('langcode') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC)); + ->fetchAll(FetchAs::Associative)); // Now load and test each revision, including the field 'field_text_plain' // which has text reflecting the revision. diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php index 304747081ad89ef1399ba50a37e1ffaefa7aa601..cbe9b346623e49bd496b8264c3fffa3f82b94320 100644 --- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php +++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\node\Kernel\Migrate\d7; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\migrate\MigrateExecutable; use Drupal\migrate_drupal\NodeMigrateType; use Drupal\node\Entity\Node; @@ -112,14 +113,14 @@ public function testNodeCompleteMigration(): void { ->orderBy('vid') ->orderBy('langcode') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC)); + ->fetchAll(FetchAs::Associative)); $this->assertEquals($this->expectedNodeFieldDataTable(), $db->select('node_field_data', 'nr') ->fields('nr') ->orderBy('nid') ->orderBy('vid') ->orderBy('langcode') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC)); + ->fetchAll(FetchAs::Associative)); // Load and test each revision. $data = $this->expectedRevisionEntityData()[0]; diff --git a/core/modules/path_alias/src/AliasRepository.php b/core/modules/path_alias/src/AliasRepository.php index 21eb3daef0d65bc2a85a14fd9285cea81c827c29..0172b8da57b6ed6d0bbbd4a381b094823f08ece7 100644 --- a/core/modules/path_alias/src/AliasRepository.php +++ b/core/modules/path_alias/src/AliasRepository.php @@ -4,6 +4,7 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Database\Query\SelectInterface; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Language\LanguageInterface; /** @@ -53,7 +54,7 @@ public function preloadPathAlias($preloaded, $langcode) { // 'base_table.id' column, as that would not guarantee other conditions // added to the query, such as those in ::addLanguageFallback, would be // reversed. - $results = $select->execute()->fetchAll(\PDO::FETCH_ASSOC); + $results = $select->execute()->fetchAll(FetchAs::Associative); $aliases = []; foreach (array_reverse($results) as $result) { $aliases[$result['path']] = $result['alias']; diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index e949cdd7652a01c0657972b83178f4b8e5443664..0a353dceed05ce51d151de60f99ed24ac0e7f1ac 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -414,6 +414,9 @@ public function mapConditionOperator($operator) { */ 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); diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Statement.php b/core/modules/sqlite/src/Driver/Database/sqlite/Statement.php index 5a2d6b826841ce5c0145d8e79d7c334a609e0d9d..1c7378a01737a61b9bab2a7db1b3b16f3d3f1834 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Statement.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Statement.php @@ -86,6 +86,10 @@ protected function getStatement(string $query, ?array &$args = []): object { * {@inheritdoc} */ public function execute($args = [], $options = []) { + if (isset($options['fetch']) && is_int($options['fetch'])) { + @trigger_error("Passing the 'fetch' key as an integer to \$options in execute() 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 { $return = parent::execute($args, $options); } diff --git a/core/modules/system/tests/modules/database_test/database_test.install b/core/modules/system/tests/modules/database_test/database_test.install index 6f4d3b7a4f8006b71369fd2c2acc635821559e27..f152becb6cd32beaa9cc09f24efe8821e8f46ae3 100644 --- a/core/modules/system/tests/modules/database_test/database_test.install +++ b/core/modules/system/tests/modules/database_test/database_test.install @@ -57,7 +57,7 @@ function database_test_schema(): array { ]; $schema['test_classtype'] = [ - 'description' => 'A duplicate version of the test table, used for fetch_style PDO::FETCH_CLASSTYPE tests.', + 'description' => 'A duplicate version of the test table, used for obsolete fetch_style PDO::FETCH_CLASSTYPE tests.', 'fields' => [ 'classname' => [ 'description' => "A custom class name", diff --git a/core/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php index 20e19439ffc227583dd6ce9b39209e5c9d3954fb..05b425557d15563263a3fd65d787359ec4ee2fa9 100644 --- a/core/modules/views/src/Plugin/views/query/Sql.php +++ b/core/modules/views/src/Plugin/views/query/Sql.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Messenger\MessengerInterface; @@ -1571,7 +1572,7 @@ public function execute(ViewExecutable $view) { } $result = $query->execute(); - $result->setFetchMode(\PDO::FETCH_CLASS, 'Drupal\views\ResultRow'); + $result->setFetchMode(FetchAs::ClassObject, 'Drupal\views\ResultRow'); // Setup the result row objects. $view->result = iterator_to_array($result); diff --git a/core/tests/Drupal/KernelTests/Core/Database/FetchLegacyTest.php b/core/tests/Drupal/KernelTests/Core/Database/FetchLegacyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6e28787f764c2f3179ed1bd92fd80743704fa0c3 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Database/FetchLegacyTest.php @@ -0,0 +1,147 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\KernelTests\Core\Database; + +use PHPUnit\Framework\Attributes\IgnoreDeprecations; + +/** + * Tests the Database system's various fetch capabilities. + * + * We get timeout errors if we try to run too many tests at once. + * + * @group Database + */ +class FetchLegacyTest extends DatabaseTestBase { + + /** + * Confirms that we can fetch a record to an object explicitly. + */ + #[IgnoreDeprecations] + public function testQueryFetchObject(): void { + $this->expectDeprecation("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"); + $this->expectDeprecation("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"); + $this->expectDeprecation("Passing the 'fetch' key as an integer to \$options in execute() 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"); + $records = []; + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_OBJ]); + foreach ($result as $record) { + $records[] = $record; + $this->assertIsObject($record); + $this->assertSame('John', $record->name); + } + + $this->assertCount(1, $records, 'There is only one record.'); + } + + /** + * Confirms that we can fetch a record to an associative array explicitly. + */ + #[IgnoreDeprecations] + public function testQueryFetchArray(): void { + $this->expectDeprecation("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"); + $this->expectDeprecation("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"); + $this->expectDeprecation("Passing the 'fetch' key as an integer to \$options in execute() 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"); + $records = []; + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_ASSOC]); + foreach ($result as $record) { + $records[] = $record; + $this->assertIsArray($record); + $this->assertArrayHasKey('name', $record); + $this->assertSame('John', $record['name']); + } + + $this->assertCount(1, $records, 'There is only one record.'); + } + + /** + * Confirms that we can fetch a record into an indexed array explicitly. + */ + #[IgnoreDeprecations] + public function testQueryFetchNum(): void { + $this->expectDeprecation("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"); + $this->expectDeprecation("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"); + $this->expectDeprecation("Passing the 'fetch' key as an integer to \$options in execute() 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"); + $records = []; + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_NUM]); + foreach ($result as $record) { + $records[] = $record; + $this->assertIsArray($record); + $this->assertArrayHasKey(0, $record); + $this->assertSame('John', $record[0]); + } + + $this->assertCount(1, $records, 'There is only one record'); + } + + /** + * Confirms that we can fetch all records into an array explicitly. + */ + #[IgnoreDeprecations] + public function testQueryFetchAllColumn(): void { + $this->expectDeprecation("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"); + $query = $this->connection->select('test'); + $query->addField('test', 'name'); + $query->orderBy('name'); + $query_result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + + $expected_result = ['George', 'John', 'Paul', 'Ringo']; + $this->assertEquals($expected_result, $query_result, 'Returned the correct result.'); + } + + /** + * Tests ::fetchAllAssoc(). + */ + #[IgnoreDeprecations] + public function testQueryFetchAllAssoc(): void { + $this->expectDeprecation("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"); + $expected_result = [ + "Singer" => [ + "id" => "2", + "name" => "George", + "age" => "27", + "job" => "Singer", + ], + "Drummer" => [ + "id" => "3", + "name" => "Ringo", + "age" => "28", + "job" => "Drummer", + ], + ]; + + $statement = $this->connection->query('SELECT * FROM {test} WHERE [age] > :age', [':age' => 26]); + $result = $statement->fetchAllAssoc('job', \PDO::FETCH_ASSOC); + $this->assertSame($expected_result, $result); + + $statement = $this->connection->query('SELECT * FROM {test} WHERE [age] > :age', [':age' => 26]); + $result = $statement->fetchAllAssoc('job', \PDO::FETCH_OBJ); + $this->assertEquals((object) $expected_result['Singer'], $result['Singer']); + $this->assertEquals((object) $expected_result['Drummer'], $result['Drummer']); + } + + /** + * Confirms that we can fetch a single column value. + */ + #[IgnoreDeprecations] + public function testQueryFetchColumn(): void { + $statement = $this->connection + ->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25]); + $statement->setFetchMode(\PDO::FETCH_COLUMN, 0); + $this->assertSame('John', $statement->fetch()); + } + + /** + * Confirms that an out of range index throws an error. + */ + #[IgnoreDeprecations] + public function testQueryFetchColumnOutOfRange(): void { + $this->expectException(\ValueError::class); + $this->expectExceptionMessage('Invalid column index'); + $statement = $this->connection + ->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25]); + $statement->setFetchMode(\PDO::FETCH_COLUMN, 200); + $statement->fetch(); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php index 62c87e32d130635a87dd414b9b96c56c585137ca..47fbd33f31a8735e8128383463bdd75c46a8e739 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Database; use Drupal\Core\Database\RowCountException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\StatementPrefetchIterator; use Drupal\Tests\system\Functional\Database\FakeRecord; @@ -41,7 +42,7 @@ public function testQueryFetchDefault(): void { public function testQueryFetchColumn(): void { $statement = $this->connection ->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25]); - $statement->setFetchMode(\PDO::FETCH_COLUMN, 0); + $statement->setFetchMode(FetchAs::Column, 0); $this->assertSame('John', $statement->fetch()); } @@ -53,7 +54,7 @@ public function testQueryFetchColumnOutOfRange(): void { $this->expectExceptionMessage('Invalid column index'); $statement = $this->connection ->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25]); - $statement->setFetchMode(\PDO::FETCH_COLUMN, 200); + $statement->setFetchMode(FetchAs::Column, 200); $statement->fetch(); } @@ -62,7 +63,7 @@ public function testQueryFetchColumnOutOfRange(): void { */ public function testQueryFetchObject(): void { $records = []; - $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_OBJ]); + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => FetchAs::Object]); foreach ($result as $record) { $records[] = $record; $this->assertIsObject($record); @@ -77,7 +78,7 @@ public function testQueryFetchObject(): void { */ public function testQueryFetchArray(): void { $records = []; - $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_ASSOC]); + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => FetchAs::Associative]); foreach ($result as $record) { $records[] = $record; $this->assertIsArray($record); @@ -146,7 +147,7 @@ public function testQueryFetchObjectClassNoConstructorArgs(): void { */ public function testQueryFetchNum(): void { $records = []; - $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_NUM]); + $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => FetchAs::List]); foreach ($result as $record) { $records[] = $record; $this->assertIsArray($record); @@ -164,7 +165,7 @@ public function testQueryFetchAllColumn(): void { $query = $this->connection->select('test'); $query->addField('test', 'name'); $query->orderBy('name'); - $query_result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + $query_result = $query->execute()->fetchAll(FetchAs::Column); $expected_result = ['George', 'John', 'Paul', 'Ringo']; $this->assertEquals($expected_result, $query_result, 'Returned the correct result.'); @@ -261,11 +262,11 @@ public function testQueryFetchAllAssoc(): void { ]; $statement = $this->connection->query('SELECT * FROM {test} WHERE [age] > :age', [':age' => 26]); - $result = $statement->fetchAllAssoc('job', \PDO::FETCH_ASSOC); + $result = $statement->fetchAllAssoc('job', FetchAs::Associative); $this->assertSame($expected_result, $result); $statement = $this->connection->query('SELECT * FROM {test} WHERE [age] > :age', [':age' => 26]); - $result = $statement->fetchAllAssoc('job', \PDO::FETCH_OBJ); + $result = $statement->fetchAllAssoc('job', FetchAs::Object); $this->assertEquals((object) $expected_result['Singer'], $result['Singer']); $this->assertEquals((object) $expected_result['Drummer'], $result['Drummer']); } diff --git a/core/tests/Drupal/KernelTests/Core/Database/SelectComplexTest.php b/core/tests/Drupal/KernelTests/Core/Database/SelectComplexTest.php index b20789bd9e3cd32e242d20a2bdf87d91195281ac..7b5f567cdae923192bddcb5247a5dfdea432676f 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/SelectComplexTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectComplexTest.php @@ -7,6 +7,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\Query\PagerSelectExtender; use Drupal\Core\Database\RowCountException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\user\Entity\User; /** @@ -185,7 +186,7 @@ public function testDistinct(): void { $query->addField('test_task', 'task'); $query->orderBy('task'); $query->distinct(); - $query_result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + $query_result = $query->execute()->fetchAll(FetchAs::Column); $expected_result = ['code', 'eat', 'found new band', 'perform at superbowl', 'sing', 'sleep']; $this->assertEquals($query_result, $expected_result, 'Returned the correct result.'); diff --git a/core/tests/Drupal/KernelTests/Core/Database/SelectOrderedTest.php b/core/tests/Drupal/KernelTests/Core/Database/SelectOrderedTest.php index b3cc7c2d2ced9a464f3489f404869fc79983f710..8a319448790f9863df212382c677ee96e61ddc76 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/SelectOrderedTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/SelectOrderedTest.php @@ -4,6 +4,8 @@ namespace Drupal\KernelTests\Core\Database; +use Drupal\Core\Database\Statement\FetchAs; + /** * Tests the Select query builder. * @@ -52,7 +54,7 @@ public function testSimpleSelectMultiOrdered(): void { ['George', 27, 'Singer'], ['Paul', 26, 'Songwriter'], ]; - $results = $result->fetchAll(\PDO::FETCH_NUM); + $results = $result->fetchAll(FetchAs::List); foreach ($expected as $k => $record) { $num_records++; foreach ($record as $kk => $col) { diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateProviderTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateProviderTest.php index 810915aff6e93525239b3bb4d05c0b4d66a64364..f8c62110eb1b96f4d7657b2eddaa9cc5794b6937 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateProviderTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateProviderTest.php @@ -4,6 +4,7 @@ namespace Drupal\KernelTests\Core\Entity; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait; @@ -152,7 +153,7 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent ->orderBy('revision_id', 'ASC') ->orderBy('langcode', 'ASC') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC); + ->fetchAll(FetchAs::Associative); $this->assertSameSize($expected, $result); // Use assertEquals and not assertSame here to prevent that a different @@ -192,7 +193,7 @@ public function testBaseFieldDeleteWithExistingData($entity_type_id, $create_ent ->orderBy('revision_id', 'ASC') ->orderBy('langcode', 'ASC') ->execute() - ->fetchAll(\PDO::FETCH_ASSOC); + ->fetchAll(FetchAs::Associative); $this->assertSameSize($expected, $result); // Use assertEquals and not assertSame here to prevent that a different diff --git a/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php b/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php index db858e1d516763e352354b925acec065eab37489..8b6d9ed52eb693069a0b5e56b5f7629fbb02f847 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Entity; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -201,7 +202,7 @@ public function testFieldWrite(): void { $connection = Database::getConnection(); // Read the tables and check the correct values have been stored. - $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', \PDO::FETCH_ASSOC); + $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', FetchAs::Associative); $this->assertCount($this->fieldCardinality, $rows); foreach ($rows as $delta => $row) { $expected = [ @@ -225,7 +226,7 @@ public function testFieldWrite(): void { $values_count = count($values); $entity->{$this->fieldName} = $values; $entity->save(); - $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', \PDO::FETCH_ASSOC); + $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', FetchAs::Associative); $this->assertCount($values_count, $rows); foreach ($rows as $delta => $row) { $expected = [ @@ -253,7 +254,7 @@ public function testFieldWrite(): void { // Check that data for both revisions are in the revision table. foreach ($revision_values as $revision_id => $values) { - $rows = $connection->select($this->revisionTable, 't')->fields('t')->condition('revision_id', $revision_id)->execute()->fetchAllAssoc('delta', \PDO::FETCH_ASSOC); + $rows = $connection->select($this->revisionTable, 't')->fields('t')->condition('revision_id', $revision_id)->execute()->fetchAllAssoc('delta', FetchAs::Associative); $this->assertCount(min(count($values), $this->fieldCardinality), $rows); foreach ($rows as $delta => $row) { $expected = [ @@ -272,7 +273,7 @@ public function testFieldWrite(): void { // Test emptying the field. $entity->{$this->fieldName} = NULL; $entity->save(); - $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', \PDO::FETCH_ASSOC); + $rows = $connection->select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', FetchAs::Associative); $this->assertCount(0, $rows); } diff --git a/core/tests/Drupal/KernelTests/Core/Menu/MenuTreeStorageTest.php b/core/tests/Drupal/KernelTests/Core/Menu/MenuTreeStorageTest.php index baa01d730b200896261686e3ca0018ee0bb1bff7..cf26cb9b3c17f2ddafc63a986fea5ab7f3ded90e 100644 --- a/core/tests/Drupal/KernelTests/Core/Menu/MenuTreeStorageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Menu/MenuTreeStorageTest.php @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Menu; use Drupal\Component\Plugin\Exception\PluginException; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Menu\MenuTreeParameters; use Drupal\Core\Menu\MenuTreeStorage; use Drupal\KernelTests\KernelTestBase; @@ -426,7 +427,7 @@ protected function assertMenuLink(string $id, array $expected_properties, array foreach ($expected_properties as $field => $value) { $query->condition($field, $value); } - $all = $query->execute()->fetchAll(\PDO::FETCH_ASSOC); + $all = $query->execute()->fetchAll(FetchAs::Associative); $this->assertCount(1, $all, "Found link $id matching all the expected properties"); $raw = reset($all); diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index 15e4cf28aa1a7f621b8e240d1090428f4b46ecb2..f9b25e8e70723b7dac051c6fd68526fbc23231b0 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -6,10 +6,13 @@ use Composer\Autoload\ClassLoader; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Statement\FetchAs; use Drupal\Core\Database\StatementPrefetchIterator; use Drupal\Tests\Core\Database\Stub\StubConnection; use Drupal\Tests\Core\Database\Stub\StubPDO; use Drupal\Tests\UnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; /** * Tests the Connection class. @@ -890,7 +893,7 @@ public static function providerMockedBacktrace(): array { * elements: * - a PDO fetch mode. */ - public static function providerSupportedFetchModes(): array { + public static function providerSupportedLegacyFetchModes(): array { return [ 'FETCH_ASSOC' => [\PDO::FETCH_ASSOC], 'FETCH_CLASS' => [\PDO::FETCH_CLASS], @@ -901,12 +904,42 @@ public static function providerSupportedFetchModes(): array { ]; } + /** + * Tests supported fetch modes. + */ + #[IgnoreDeprecations] + #[DataProvider('providerSupportedLegacyFetchModes')] + public function testSupportedLegacyFetchModes(int $mode): void { + $this->expectDeprecation("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"); + $mockPdo = $this->createMock(StubPDO::class); + $mockConnection = new StubConnection($mockPdo, []); + $statement = new StatementPrefetchIterator($mockPdo, $mockConnection, ''); + $this->assertInstanceOf(StatementPrefetchIterator::class, $statement); + $statement->setFetchMode($mode); + } + + /** + * Provides data for testSupportedFetchModes. + * + * @return array<string,array<\Drupal\Core\Database\FetchAs>> + * The FetchAs cases. + */ + public static function providerSupportedFetchModes(): array { + return [ + 'Associative array' => [FetchAs::Associative], + 'Classed object' => [FetchAs::ClassObject], + 'Single column' => [FetchAs::Column], + 'Simple array' => [FetchAs::List], + 'Standard object' => [FetchAs::Object], + ]; + } + /** * Tests supported fetch modes. * * @dataProvider providerSupportedFetchModes */ - public function testSupportedFetchModes(int $mode): void { + public function testSupportedFetchModes(FetchAs $mode): void { $mockPdo = $this->createMock(StubPDO::class); $mockConnection = new StubConnection($mockPdo, []); $statement = new StatementPrefetchIterator($mockPdo, $mockConnection, ''); @@ -937,12 +970,13 @@ public static function providerUnsupportedFetchModes(): array { } /** - * Tests unsupported fetch modes. - * - * @dataProvider providerUnsupportedFetchModes + * Tests unsupported legacy fetch modes. */ + #[IgnoreDeprecations] + #[DataProvider('providerUnsupportedFetchModes')] public function testUnsupportedFetchModes(int $mode): void { - $this->expectException(\AssertionError::class); + $this->expectDeprecation("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"); + $this->expectException(\RuntimeException::class); $this->expectExceptionMessageMatches("/^Fetch mode FETCH_.* is not supported\\. Use supported modes only/"); $mockPdo = $this->createMock(StubPDO::class); $mockConnection = new StubConnection($mockPdo, []);