Issue #3112476 by alexpott, Neslee Canil Pinto, mondrake, daffie, salah1, neelam_wadhwani: Always set $info['namespace'] on database connection info

merge-requests/2419/head
catch 2020-04-15 18:27:31 +01:00
parent 4994ca42d1
commit 2989dd00ba
9 changed files with 110 additions and 38 deletions

View File

@ -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') {

View File

@ -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'];
}

View File

@ -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.
*

View File

@ -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)) {

View File

@ -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);

View File

@ -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']);
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Log;
use Drupal\Tests\Core\Database\Stub\StubConnection;
use Drupal\Tests\Core\Database\Stub\StubPDO;
use Drupal\Tests\UnitTestCase;
/**
* Tests the Log class.
*
* @group Database
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @coversDefaultClass \Drupal\Core\Database\Log
*/
class LogTest extends UnitTestCase {
/**
* Tests that a log called by a custom database driver returns proper caller.
*
* @covers ::findCaller
*/
public function testContribDriverLog() {
Database::addConnectionInfo('default', 'default', [
'driver' => '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);
}
}

View File

@ -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);
}

View File

@ -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();
}
}