diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index 5b16086..064adc2 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -23,7 +23,7 @@ * * @see http://php.net/manual/book.pdo.php */ -abstract class Connection extends PDO { +abstract class Connection implements \Serializable { /** * The database target this connection is for. @@ -101,6 +101,13 @@ protected $temporaryNameIndex = 0; /** + * The actual PDO connection. + * + * @var \PDO + */ + protected $connection; + + /** * The connection information for this connection object. * * @var array @@ -135,23 +142,34 @@ */ protected $prefixReplace = array(); - function __construct($dsn, $username, $password, $driver_options = array()) { + /** + * Constructs a Connection object. + */ + public function __construct(PDO $connection, array $connection_options) { // Initialize and prepare the connection prefix. - $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); - - // Because the other methods don't seem to work right. - $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; - - // Call PDO::__construct and PDO::setAttribute. - parent::__construct($dsn, $username, $password, $driver_options); + $this->setPrefix(isset($connection_options['prefix']) ? $connection_options['prefix'] : ''); // Set a Statement class, unless the driver opted out. if (!empty($this->statementClass)) { - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); + $connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); } + + $this->connection = $connection; + $this->connectionOptions = $connection_options; } /** + * Opens a PDO connection. + * + * @param array $connection_options + * The database connection settings array. + * + * @return \PDO + * A \PDO object. + */ + public static function open(array &$connection_options = array()) { } + + /** * Destroys this Connection object. * * PHP does not destruct an object if it is still referenced in other @@ -163,7 +181,7 @@ public function destroy() { // Destroy all references to this connection by setting them to NULL. // The Statement class attribute only accepts a new value that presents a // proper callable, so we reset it to PDOStatement. - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + $this->connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); $this->schema = NULL; } @@ -318,8 +336,7 @@ public function tablePrefix($table = 'default') { public function prepareQuery($query) { $query = $this->prefixTables($query); - // Call PDO::prepare. - return parent::prepare($query); + return $this->connection->prepare($query); } /** @@ -532,7 +549,7 @@ public function query($query, array $args = array(), $options = array()) { case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: - return $this->lastInsertId(); + return $this->connection->lastInsertId(); case Database::RETURN_NULL: return; default: @@ -921,7 +938,7 @@ public function rollback($savepoint_name = 'drupal_transaction') { $rolled_back_other_active_savepoints = TRUE; } } - parent::rollBack(); + $this->connection->rollBack(); if ($rolled_back_other_active_savepoints) { throw new TransactionOutOfOrderException(); } @@ -949,7 +966,7 @@ public function pushTransaction($name) { $this->query('SAVEPOINT ' . $name); } else { - parent::beginTransaction(); + $this->connection->beginTransaction(); } $this->transactionLayers[$name] = $name; } @@ -1000,7 +1017,7 @@ protected function popCommittableTransactions() { // If there are no more layers left then we should commit. unset($this->transactionLayers[$name]); if (empty($this->transactionLayers)) { - if (!parent::commit()) { + if (!$this->connection->commit()) { throw new TransactionCommitFailedException(); } } @@ -1084,7 +1101,7 @@ protected function generateTemporaryTableName() { * Returns the version of the database server. */ public function version() { - return $this->getAttribute(PDO::ATTR_SERVER_VERSION); + return $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION); } /** @@ -1178,4 +1195,76 @@ public function commit() { * also larger than the $existing_id if one was passed in. */ abstract public function nextId($existing_id = 0); + + /** + * Prepares a statement for execution and returns a statement object + * + * Emulated prepared statements does not communicate with the database server + * so this method does not check the statement. + * + * @param string $statement + * This must be a valid SQL statement for the target database server. + * @param array $driver_options + * (optional) This array holds one or more key=>value pairs to set + * attribute values for the PDOStatement object that this method returns. + * You would most commonly use this to set the \PDO::ATTR_CURSOR value to + * \PDO::CURSOR_SCROLL to request a scrollable cursor. Some drivers have + * driver specific options that may be set at prepare-time. Defaults to an + * empty array. + * + * @return \PDOStatement|false + * If the database server successfully prepares the statement, returns a + * \PDOStatement object. + * If the database server cannot successfully prepare the statement returns + * FALSE or emits \PDOException (depending on error handling). + * + * @throws \PDOException + * + * @see \PDO::prepare() + */ + public function prepare($statement, array $driver_options = array()) { + return $this->connection->prepare($statement, $driver_options); + } + + /** + * Quotes a string for use in a query. + * + * @param string $string + * The string to be quoted. + * @param int $parameter_type + * (optional) Provides a data type hint for drivers that have alternate + * quoting styles. Defaults to \PDO::PARAM_STR. + * + * @return string|bool + * A quoted string that is theoretically safe to pass into an SQL statement. + * Returns FALSE if the driver does not support quoting in this way. + * + * @see \PDO::quote() + */ + public function quote($string, $parameter_type = \PDO::PARAM_STR) { + return $this->connection->quote($string, $parameter_type); + } + + /** + * {@inheritdoc} + */ + public function serialize() { + $connection = clone $this; + // Don't serialize the PDO connection and other lazy-instantiated members. + unset($connection->connection, $connection->schema, $connection->driverClasses); + return serialize(get_object_vars($connection)); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { + $data = unserialize($serialized); + foreach ($data as $key => $value) { + $this->{$key} = $value; + } + // Re-establish the PDO connection using the original options. + $this->connection = static::open($this->connectionOptions); + } + } diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php index a54c9cb..d6c28ca 100644 --- a/core/lib/Drupal/Core/Database/Database.php +++ b/core/lib/Drupal/Core/Database/Database.php @@ -381,7 +381,9 @@ public static function addConnectionInfo($key, $target, $info) { // Fallback for Drupal 7 settings.php. $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection"; } - $new_connection = new $driver_class(self::$databaseInfo[$key][$target]); + + $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]); + $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]); $new_connection->setTarget($target); $new_connection->setKey($key); diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php index 128780f..9e0e65f 100644 --- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php @@ -36,7 +36,12 @@ class Connection extends DatabaseConnection { */ protected $needsCleanup = FALSE; - public function __construct(array $connection_options = array()) { + /** + * Constructs a Connection object. + */ + public function __construct(PDO $connection, array $connection_options = array()) { + parent::__construct($connection, $connection_options); + // This driver defaults to transaction support, except if explicitly passed FALSE. $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); @@ -44,7 +49,12 @@ public function __construct(array $connection_options = array()) { $this->transactionalDDLSupport = FALSE; $this->connectionOptions = $connection_options; + } + /** + * {@inheritdoc} + */ + public static function open(array &$connection_options = array()) { // The DSN should use either a socket or a host/port. if (isset($connection_options['unix_socket'])) { $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket']; @@ -61,22 +71,23 @@ public function __construct(array $connection_options = array()) { 'pdo' => array(), ); $connection_options['pdo'] += array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // So we don't have to mess around with cursors and unbuffered queries by default. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, // Because MySQL's prepared statements skip the query cache, because it's dumb. PDO::ATTR_EMULATE_PREPARES => TRUE, ); - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); + $pdo = new PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); // Force MySQL to use the UTF-8 character set. Also set the collation, if a // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' // for UTF-8. if (!empty($connection_options['collation'])) { - $this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']); + $pdo->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']); } else { - $this->exec('SET NAMES utf8'); + $pdo->exec('SET NAMES utf8'); } // Set MySQL init_commands if not already defined. Default Drupal's MySQL @@ -94,7 +105,9 @@ public function __construct(array $connection_options = array()) { 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", ); // Set connection options. - $this->exec(implode('; ', $connection_options['init_commands'])); + $pdo->exec(implode('; ', $connection_options['init_commands'])); + + return $pdo; } public function __destruct() { @@ -135,8 +148,8 @@ public function createDatabase($database) { try { // Create the database and set it as active. - $this->exec("CREATE DATABASE $database"); - $this->exec("USE $database"); + $this->connection->exec("CREATE DATABASE $database"); + $this->connection->exec("USE $database"); } catch (\Exception $e) { throw new DatabaseNotFoundException($e->getMessage()); @@ -204,7 +217,7 @@ protected function popCommittableTransactions() { // If there are no more layers left then we should commit. unset($this->transactionLayers[$name]); if (empty($this->transactionLayers)) { - if (!PDO::commit()) { + if (!$this->connection->commit()) { throw new TransactionCommitFailedException(); } } @@ -227,7 +240,7 @@ protected function popCommittableTransactions() { $this->transactionLayers = array(); // We also have to explain to PDO that the transaction stack has // been cleaned-up. - PDO::commit(); + $this->connection->commit(); } else { throw $e; diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index 4fd7d0d..30e9e01 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -34,7 +34,12 @@ class Connection extends DatabaseConnection { */ const DATABASE_NOT_FOUND = 7; - public function __construct(array $connection_options = array()) { + /** + * Constructs a connection object. + */ + public function __construct(PDO $connection, array $connection_options) { + parent::__construct($connection, $connection_options); + // This driver defaults to transaction support, except if explicitly passed FALSE. $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); @@ -42,6 +47,21 @@ public function __construct(array $connection_options = array()) { // but we'll only enable it if standard transactions are. $this->transactionalDDLSupport = $this->transactionSupport; + $this->connectionOptions = $connection_options; + + // Force PostgreSQL to use the UTF-8 character set by default. + $this->connection->exec("SET NAMES 'UTF8'"); + + // Execute PostgreSQL init_commands. + if (isset($connection_options['init_commands'])) { + $this->connection->exec(implode('; ', $connection_options['init_commands'])); + } + } + + /** + * {@inheritdoc} + */ + public static function open(array &$connection_options = array()) { // Default to TCP connection on port 5432. if (empty($connection_options['port'])) { $connection_options['port'] = 5432; @@ -61,8 +81,6 @@ public function __construct(array $connection_options = array()) { $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']); } - $this->connectionOptions = $connection_options; - $connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1'); $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; @@ -71,6 +89,7 @@ public function __construct(array $connection_options = array()) { 'pdo' => array(), ); $connection_options['pdo'] += array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Prepared statements are most effective for performance when queries // are recycled (used several times). However, if they are not re-used, // prepared statements become ineffecient. Since most of Drupal's @@ -81,17 +100,12 @@ public function __construct(array $connection_options = array()) { // Convert numeric values to strings when fetching. PDO::ATTR_STRINGIFY_FETCHES => TRUE, ); - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); + $pdo = new PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); - // Force PostgreSQL to use the UTF-8 character set by default. - $this->exec("SET NAMES 'UTF8'"); - - // Execute PostgreSQL init_commands. - if (isset($connection_options['init_commands'])) { - $this->exec(implode('; ', $connection_options['init_commands'])); - } + return $pdo; } + public function query($query, array $args = array(), $options = array()) { $options += $this->defaultOptions(); @@ -124,7 +138,7 @@ public function query($query, array $args = array(), $options = array()) { case Database::RETURN_AFFECTED: return $stmt->rowCount(); case Database::RETURN_INSERT_ID: - return $this->lastInsertId($options['sequence_name']); + return $this->connection->lastInsertId($options['sequence_name']); case Database::RETURN_NULL: return; default: diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php index abda556..7d05b1f 100644 --- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php @@ -66,7 +66,12 @@ class Connection extends DatabaseConnection { */ var $tableDropped = FALSE; - public function __construct(array $connection_options = array()) { + /** + * Constructs a \Drupal\Core\Database\Driver\sqlite\Connection object. + */ + public function __construct(PDO $connection, array $connection_options) { + parent::__construct($connection, $connection_options); + // We don't need a specific PDOStatement class here, we simulate it below. $this->statementClass = NULL; @@ -75,16 +80,6 @@ public function __construct(array $connection_options = array()) { $this->connectionOptions = $connection_options; - // Allow PDO options to be overridden. - $connection_options += array( - 'pdo' => array(), - ); - $connection_options['pdo'] += array( - // Convert numeric values to strings when fetching. - PDO::ATTR_STRINGIFY_FETCHES => TRUE, - ); - parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']); - // Attach one database for each registered prefix. $prefixes = $this->prefixes; foreach ($prefixes as $table => &$prefix) { @@ -107,24 +102,43 @@ public function __construct(array $connection_options = array()) { // Detect support for SAVEPOINT. $version = $this->query('SELECT sqlite_version()')->fetchField(); $this->savepointSupport = (version_compare($version, '3.6.8') >= 0); + } + + /** + * {@inheritdoc} + */ + public static function open(array &$connection_options = array()) { + // Allow PDO options to be overridden. + $connection_options += array( + 'pdo' => array(), + ); + $connection_options['pdo'] += array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + // Convert numeric values to strings when fetching. + PDO::ATTR_STRINGIFY_FETCHES => TRUE, + ); + $pdo = new PDO('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']); // Create functions needed by SQLite. - $this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf')); - $this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest')); - $this->sqliteCreateFunction('pow', 'pow', 2); - $this->sqliteCreateFunction('length', 'strlen', 1); - $this->sqliteCreateFunction('md5', 'md5', 1); - $this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat')); - $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3); - $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3); - $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); + $pdo->sqliteCreateFunction('if', array(__CLASS__, 'sqlFunctionIf')); + $pdo->sqliteCreateFunction('greatest', array(__CLASS__, 'sqlFunctionGreatest')); + $pdo->sqliteCreateFunction('pow', 'pow', 2); + $pdo->sqliteCreateFunction('length', 'strlen', 1); + $pdo->sqliteCreateFunction('md5', 'md5', 1); + $pdo->sqliteCreateFunction('concat', array(__CLASS__, 'sqlFunctionConcat')); + $pdo->sqliteCreateFunction('substring', array(__CLASS__, 'sqlFunctionSubstring'), 3); + $pdo->sqliteCreateFunction('substring_index', array(__CLASS__, 'sqlFunctionSubstringIndex'), 3); + $pdo->sqliteCreateFunction('rand', array(__CLASS__, 'sqlFunctionRand')); // Execute sqlite init_commands. if (isset($connection_options['init_commands'])) { - $this->exec(implode('; ', $connection_options['init_commands'])); + $pdo->exec(implode('; ', $connection_options['init_commands'])); } + + return $pdo; } + /** * Destructor for the SQLite connection. * @@ -158,14 +172,14 @@ public function __destruct() { /** * SQLite compatibility implementation for the IF() SQL function. */ - public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) { + public static function sqlFunctionIf($condition, $expr1, $expr2 = NULL) { return $condition ? $expr1 : $expr2; } /** * SQLite compatibility implementation for the GREATEST() SQL function. */ - public function sqlFunctionGreatest() { + public static function sqlFunctionGreatest() { $args = func_get_args(); foreach ($args as $k => $v) { if (!isset($v)) { @@ -183,7 +197,7 @@ public function sqlFunctionGreatest() { /** * SQLite compatibility implementation for the CONCAT() SQL function. */ - public function sqlFunctionConcat() { + public static function sqlFunctionConcat() { $args = func_get_args(); return implode('', $args); } @@ -191,14 +205,14 @@ public function sqlFunctionConcat() { /** * SQLite compatibility implementation for the SUBSTRING() SQL function. */ - public function sqlFunctionSubstring($string, $from, $length) { + public static function sqlFunctionSubstring($string, $from, $length) { return substr($string, $from - 1, $length); } /** * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function. */ - public function sqlFunctionSubstringIndex($string, $delimiter, $count) { + public static function sqlFunctionSubstringIndex($string, $delimiter, $count) { // If string is empty, simply return an empty string. if (empty($string)) { return ''; @@ -247,7 +261,7 @@ public function prepare($query, $options = array()) { * the world. */ public function PDOPrepare($query, array $options = array()) { - return parent::prepare($query, $options); + return $this->connection->prepare($query, $options); } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { @@ -354,7 +368,7 @@ public function rollback($savepoint_name = 'drupal_transaction') { } } if ($this->supportsTransactions()) { - PDO::rollBack(); + $this->connection->rollBack(); } } @@ -394,9 +408,9 @@ public function popTransaction($name) { // If there was any rollback() we should roll back whole transaction. if ($this->willRollback) { $this->willRollback = FALSE; - PDO::rollBack(); + $this->connection->rollBack(); } - elseif (!PDO::commit()) { + elseif (!$this->connection->commit()) { throw new TransactionCommitFailedException(); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionTest.php index 8bdc45e..c20d83d 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionTest.php @@ -123,4 +123,5 @@ function testConnectionOptions() { $connectionOptions = $db->getConnectionOptions(); $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); } + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php index 3bba870..4bcc54f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/ConnectionUnitTest.php @@ -226,4 +226,64 @@ function testOpenSelectQueryClose() { $this->assertNoConnection($id); } + /** + * Tests the serialization and unserialization of a database connection. + */ + public function testConnectionSerialization() { + $db = Database::getConnection('default', 'default'); + + try { + $serialized = serialize($db); + $this->pass('The database connection can be serialized.'); + + $unserialized = unserialize($serialized); + $this->assertTrue(get_class($unserialized) === get_class($db)); + } + catch (\Exception $e) { + $this->fail('The database connection cannot be serialized.'); + } + + // Ensure that all properties on the unserialized object are the same. + $db_reflection = new \ReflectionObject($db); + $unserialized_reflection = new \ReflectionObject($unserialized); + foreach ($db_reflection->getProperties() as $value) { + // Skip the pdo connection object. + if ($value->getName() == 'connection') { + continue; + } + $value->setAccessible(TRUE); + $unserialized_property = $unserialized_reflection->getProperty($value->getName()); + $unserialized_property->setAccessible(TRUE); + $this->assertEqual($unserialized_property->getValue($unserialized), $value->getValue($db)); + } + + } + + /** + * Tests pdo options override. + */ + public function testConnectionOpen() { + $connection = Database::getConnection('default'); + $reflection = new \ReflectionObject($connection); + $connection_property = $reflection->getProperty('connection'); + $connection_property->setAccessible(TRUE); + $error_mode = $connection_property->getValue($connection) + ->getAttribute(\PDO::ATTR_ERRMODE); + $this->assertEqual($error_mode, \PDO::ERRMODE_EXCEPTION, 'Ensure the default error mode is set to exception.'); + + $connection = Database::getConnectionInfo('default'); + $connection['default']['pdo'][\PDO::ATTR_ERRMODE] = \PDO::ERRMODE_SILENT; + Database::addConnectionInfo('test', 'default', $connection['default']); + $connection = Database::getConnection('default', 'test'); + + $reflection = new \ReflectionObject($connection); + $connection_property = $reflection->getProperty('connection'); + $connection_property->setAccessible(TRUE); + $error_mode = $connection_property->getValue($connection) + ->getAttribute(\PDO::ATTR_ERRMODE); + $this->assertEqual($error_mode, \PDO::ERRMODE_SILENT, 'Ensure PDO connection options can be overridden.'); + + Database::removeConnection('test'); + } + } diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php index c662ec7..f402395 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php @@ -664,4 +664,13 @@ function testMultiFormSameNameErrorClass() { $this->assertFieldByXpath('//input[@id="edit-name" and contains(@class, "error")]', NULL, 'Error input form element class found for first element.'); $this->assertNoFieldByXpath('//input[@id="edit-name--2" and contains(@class, "error")]', NULL, 'No error input form element class found for second element.'); } + + /** + * Tests a form with a form state storing a database connection. + */ + public function testFormStateDatabaseConnection() { + $this->assertNoText('Database connection found'); + $this->drupalPost('form-test/form_state-database', array(), t('Submit')); + $this->assertText('Database connection found'); + } } diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module index c7a85a3..97657a1 100644 --- a/core/modules/system/tests/modules/form_test/form_test.module +++ b/core/modules/system/tests/modules/form_test/form_test.module @@ -5,6 +5,8 @@ * Helper module for the form API tests. */ +use Drupal\Core\Database\Connection; +use Drupal\Core\Database\Database; use Drupal\form_test\Callbacks; use Drupal\form_test\FormTestObject; use Drupal\form_test\SystemConfigFormTestForm; @@ -353,6 +355,13 @@ function form_test_menu() { 'access callback' => TRUE, ); + $items['form-test/form_state-database'] = array( + 'title' => t('Form state with a database connection'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_form_state_database'), + 'access callback' => TRUE, + ); + return $items; } @@ -2466,3 +2475,40 @@ function form_test_group_vertical_tabs() { ); return $form; } + +/** + * Builds a form which gets the database connection stored in the form state. + */ +function form_test_form_state_database($form, &$form_state) { + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text field'), + ); + + $form['test_submit'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + + $db = Database::getConnection('default'); + $form_state['storage']['database'] = $db; + $form_state['storage']['database_class'] = get_class($db); + + if (isset($form_state['storage']['database_connection_found'])) { + $form['database']['#markup'] = 'Database connection found'; + } + + return $form; +} + +/** + * Form submit handler for database form_state test. + */ +function form_test_form_state_database_submit($form, &$form_state) { + $form_state['cache'] = TRUE; + $form_state['rebuild'] = TRUE; + + if ($form_state['storage']['database'] instanceof $form_state['storage']['database_class']) { + $form_state['storage']['database_connection_found'] = TRUE; + } +}