Commit aebcf1dd authored by catch's avatar catch

Issue #3112476 by alexpott, Neslee Canil Pinto, mondrake, daffie, salah1,...

Issue #3112476 by alexpott, Neslee Canil Pinto, mondrake, daffie, salah1, neelam_wadhwani: Always set $info['namespace'] on database connection info
parent 1cd0b857
......@@ -216,6 +216,13 @@ public function __construct(\PDO $connection, array $connection_options) {
}
assert(count($this->identifierQuotes) === 2 && Inspector::assertAllStrings($this->identifierQuotes), '\Drupal\Core\Database\Connection::$identifierQuotes must contain 2 string values');
// 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'] : '');
......@@ -865,10 +872,6 @@ protected function expandArguments(&$query, &$args) {
*/
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') {
......
......@@ -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 @@ public static function getConnectionInfoAsUrl($key = 'default') {
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
......
......@@ -73,8 +73,6 @@ public function __construct(\PDO $connection, array $connection_options) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
$this->connectionOptions = $connection_options;
// Attach one database for each registered prefix.
$prefixes = $this->prefixes;
foreach ($prefixes as &$prefix) {
......
......@@ -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 @@ public function log(StatementInterface $statement, $args, $time) {
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 @@ public function findCaller() {
// 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 @@ public function findCaller() {
}
}
/**
* 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.
*
......
......@@ -46,8 +46,9 @@ public function resetLoggedStatements() {
* {@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)) {
......
......@@ -155,14 +155,13 @@ public function testGetLoggingWrongKey() {
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);
......
......@@ -432,4 +432,13 @@ public function testIdentifierQuotesAssertString() {
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']);
}
}
<?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);
}
}
......@@ -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);
}
......
......@@ -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 @@ public function nextId($existing_id = 0) {
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();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment