diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php index c3bfff0d512..919261ab21d 100644 --- a/core/lib/Drupal/Core/Database/Connection.php +++ b/core/lib/Drupal/Core/Database/Connection.php @@ -214,6 +214,13 @@ abstract class Connection { unset($connection_options['transactions']); } + // Work out the database driver namespace if none is provided. This normally + // written to setting.php by installer or set by + // \Drupal\Core\Database\Database::parseConnectionInfo(). + if (empty($connection_options['namespace'])) { + $connection_options['namespace'] = (new \ReflectionObject($this))->getNamespaceName(); + } + // Initialize and prepare the connection prefix. $this->setPrefix(isset($connection_options['prefix']) ? $connection_options['prefix'] : ''); @@ -863,10 +870,6 @@ abstract class Connection { */ public function getDriverClass($class) { if (empty($this->driverClasses[$class])) { - if (empty($this->connectionOptions['namespace'])) { - // Fallback for Drupal 7 settings.php and the test runner script. - $this->connectionOptions['namespace'] = (new \ReflectionObject($this))->getNamespaceName(); - } $driver_class = $this->connectionOptions['namespace'] . '\\' . $class; $this->driverClasses[$class] = class_exists($driver_class) ? $driver_class : $class; if ($this->driverClasses[$class] === 'Condition') { diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php index 08e2146bd81..719dd29c4d2 100644 --- a/core/lib/Drupal/Core/Database/Database.php +++ b/core/lib/Drupal/Core/Database/Database.php @@ -214,6 +214,7 @@ abstract class Database { if (empty($info['driver'])) { $info = $info[mt_rand(0, count($info) - 1)]; } + // Parse the prefix information. if (!isset($info['prefix'])) { // Default to an empty prefix. @@ -227,6 +228,12 @@ abstract class Database { 'default' => $info['prefix'], ]; } + + // Fallback for Drupal 7 settings.php if namespace is not provided. + if (empty($info['namespace'])) { + $info['namespace'] = 'Drupal\\Core\\Database\\Driver\\' . $info['driver']; + } + return $info; } @@ -368,8 +375,7 @@ abstract class Database { throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); } - $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]); - $driver_class = $namespace . '\\Connection'; + $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection'; $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]); $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]); @@ -605,7 +611,7 @@ abstract class Database { if (empty($db_info) || empty($db_info['default'])) { throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings"); } - $namespace = static::getDatabaseDriverNamespace($db_info['default']); + $namespace = $db_info['default']['namespace']; // If the driver namespace is within a Drupal module, add the module name // to the connection options to make it easy for the connection class's @@ -628,8 +634,14 @@ abstract class Database { * * @return string * The PHP namespace of the driver's database. + * + * @deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. There is no + * replacement as $connection_info['namespace'] is always set. + * + * @see https://www.drupal.org/node/3127769 */ protected static function getDatabaseDriverNamespace(array $connection_info) { + @trigger_error(__METHOD__ . " is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. There is no replacement as \$connection_info['namespace'] is always set. See https://www.drupal.org/node/3127769.", E_USER_DEPRECATED); if (isset($connection_info['namespace'])) { return $connection_info['namespace']; } diff --git a/core/lib/Drupal/Core/Database/Log.php b/core/lib/Drupal/Core/Database/Log.php index ce7780eb0ad..49d00cfc283 100644 --- a/core/lib/Drupal/Core/Database/Log.php +++ b/core/lib/Drupal/Core/Database/Log.php @@ -38,13 +38,6 @@ class Log { */ protected $connectionKey = 'default'; - /** - * The PHP namespace of the database driver that this object is logging. - * - * @var string - */ - protected $driverNamespace; - /** * Constructor. * @@ -152,6 +145,8 @@ class Log { public function findCaller() { $stack = $this->getDebugBacktrace(); + $driver_namespace = Database::getConnectionInfo($this->connectionKey)['default']['namespace']; + // Starting from the very first entry processed during the request, find // the first function call that can be identified as a call to a // method/function in the database layer. @@ -160,7 +155,7 @@ class Log { // it a default empty string value in that case. $class = $stack[$n]['class'] ?? ''; - if (strpos($class, __NAMESPACE__, 0) === 0 || strpos($class, $this->getDriverNamespace(), 0) === 0) { + if (strpos($class, __NAMESPACE__, 0) === 0 || strpos($class, $driver_namespace, 0) === 0) { break; } } @@ -181,20 +176,6 @@ class Log { } } - /** - * Gets the namespace of the database driver. - * - * @return string|null - * Namespace of the database driver, or NULL if the connection is - * missing. - */ - protected function getDriverNamespace() { - if (!isset($this->driverNamespace)) { - $this->driverNamespace = (new \ReflectionObject(Database::getConnection('default', $this->connectionKey)))->getNamespaceName(); - } - return $this->driverNamespace; - } - /** * Gets the debug backtrace. * diff --git a/core/modules/system/tests/modules/database_statement_monitoring_test/src/LoggedStatementsTrait.php b/core/modules/system/tests/modules/database_statement_monitoring_test/src/LoggedStatementsTrait.php index d4f760e093c..72de5448297 100644 --- a/core/modules/system/tests/modules/database_statement_monitoring_test/src/LoggedStatementsTrait.php +++ b/core/modules/system/tests/modules/database_statement_monitoring_test/src/LoggedStatementsTrait.php @@ -46,8 +46,9 @@ trait LoggedStatementsTrait { * {@inheritdoc} */ public function getDriverClass($class) { - // Override because the base class uses reflection to determine namespace - // based on object, which would break. + // Override because the database driver copies in the + // database_statement_monitoring_test module don't contain all the necessary + // classes. $namespace = (new \ReflectionClass(get_parent_class($this)))->getNamespaceName(); $driver_class = $namespace . '\\' . $class; if (class_exists($driver_class)) { diff --git a/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php b/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php index daa6a15a5cd..3b4a920c776 100644 --- a/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php +++ b/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php @@ -155,14 +155,13 @@ class LoggingTest extends DatabaseTestBase { public function testContribDriverLog($driver_namespace, $stack, array $expected_entry) { $mock_builder = $this->getMockBuilder(Log::class); $log = $mock_builder - ->setMethods(['getDriverNamespace', 'getDebugBacktrace']) + ->setMethods(['getDebugBacktrace']) + ->setConstructorArgs(['test']) ->getMock(); - $log->expects($this->any()) - ->method('getDriverNamespace') - ->will($this->returnValue($driver_namespace)); $log->expects($this->once()) ->method('getDebugBacktrace') ->will($this->returnValue($stack)); + Database::addConnectionInfo('test', 'default', ['driver' => 'mysql', 'namespace' => $driver_namespace]); $result = $log->findCaller($stack); $this->assertEquals($expected_entry, $result); diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php index ceb5cb9761c..80055026677 100644 --- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php +++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php @@ -432,4 +432,13 @@ class ConnectionTest extends UnitTestCase { new StubConnection($mock_pdo, [], [0, '1']); } + /** + * @covers ::__construct + */ + public function testNamespaceDefault() { + $mock_pdo = $this->createMock(StubPDO::class); + $connection = new StubConnection($mock_pdo, []); + $this->assertSame('Drupal\Tests\Core\Database\Stub', $connection->getConnectionOptions()['namespace']); + } + } diff --git a/core/tests/Drupal/Tests/Core/Database/LogTest.php b/core/tests/Drupal/Tests/Core/Database/LogTest.php new file mode 100644 index 00000000000..7184be4dc2c --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Database/LogTest.php @@ -0,0 +1,55 @@ + 'test', + 'namespace' => 'Drupal\Tests\Core\Database\Stub', + ]); + + $pdo = $this->prophesize(StubPDO::class)->reveal(); + $result = (new StubConnection($pdo, []))->testLogCaller(); + $this->assertSame([ + 'file' => __FILE__, + 'line' => 33, + 'function' => 'testContribDriverLog', + 'class' => 'Drupal\Tests\Core\Database\LogTest', + 'type' => '->', + 'args' => [], + ], $result); + + // Test calling the database log from outside of database code. + $result = (new Log())->findCaller(); + $this->assertSame([ + 'file' => __FILE__, + 'line' => 44, + 'function' => 'testContribDriverLog', + 'class' => 'Drupal\Tests\Core\Database\LogTest', + 'type' => '->', + 'args' => [], + ], $result); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Database/OrderByTest.php b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php index 4f330a426fc..8f7427ed3ea 100644 --- a/core/tests/Drupal/Tests/Core/Database/OrderByTest.php +++ b/core/tests/Drupal/Tests/Core/Database/OrderByTest.php @@ -3,6 +3,8 @@ namespace Drupal\Tests\Core\Database; use Drupal\Core\Database\Query\Select; +use Drupal\Tests\Core\Database\Stub\StubConnection; +use Drupal\Tests\Core\Database\Stub\StubPDO; use Drupal\Tests\UnitTestCase; /** @@ -23,9 +25,8 @@ class OrderByTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp(): void { - $connection = $this->getMockBuilder('Drupal\Core\Database\Connection') - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $mockPdo = $this->createMock(StubPDO::class); + $connection = new StubConnection($mockPdo, []); $this->query = new Select($connection, 'test', NULL); } diff --git a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php index db1a3809b29..eb8d9487b68 100644 --- a/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php +++ b/core/tests/Drupal/Tests/Core/Database/Stub/StubConnection.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\Core\Database\Stub; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\Log; use Drupal\Core\Database\StatementEmpty; /** @@ -84,4 +85,14 @@ class StubConnection extends Connection { return 0; } + /** + * Helper method to test database classes are not included in backtraces. + * + * @return array + * The caller stack entry. + */ + public function testLogCaller() { + return (new Log())->findCaller(); + } + }