Skip to content

Commit 6b80e4f

Browse files
authored
[Core] Remove Database::singleton function (#8427)
There are currently 3 methods of getting a reference to a database in LORIS, in order of preference: 1. LorisInstance->getDatabaseConnection() 2. NDB_Factory::singleton()->database(); 3. Database::singleton() The only reference to 3 left is in the implementation of option 2. This removes the Database::singleton by inlineing the reference into the factory and removes all remaining references to Database::singleton (mostly from documentation and a couple unit tests.)
1 parent 2f21693 commit 6b80e4f

File tree

10 files changed

+69
-126
lines changed

10 files changed

+69
-126
lines changed

docs/CodingStandards.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ Prepared statements MUST be used for any database interactions that use user inp
3232

3333
LORIS has many classes that use a Singleton design pattern. To facilitate with
3434
unit testing, it is best to use these singletons via the NDB_Factory class.
35-
For example, you should use the Database class like this:
35+
For example, you should use the Project class like this:
3636

3737
```php
38-
$database = \NDB_Factory::singleton()->database();
38+
$project = \NDB_Factory::singleton()->project("controlproject");
3939
```
4040

4141
instead of
4242

4343
```php
44-
$database = \Database::singleton();
44+
$project = \Project::singleton("controlproject");
4545
```
4646

4747
# HTML

docs/instruments/NDB_BVL_Instrument_UPLOADER_TEMPLATE.class.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class NDB_BVL_Instrument_TEST_NAME extends NDB_BVL_Instrument
189189
function importFile(&$file, $args) {
190190
$fp=fopen($file->fileInfo['tmp_name'], "r");
191191

192-
$db=& Database::singleton();
192+
$db= $this->loris->getDatabaseConnection();
193193
///Setting trackchanges to false because getting error messages
194194
$db->_trackChanges = false;
195195
////////////////////////////////////////////////////////////////

docs/wiki/99_Developers/UnitTestGuide.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,6 @@ This can be used for almost any class within LORIS. In the section right below,
395395
396396
397397
```
398-
$DB = \Database::singleton();
399398
$config = \NDB_Config::singleton();
400399
$user = \User::singleton();
401400
```
@@ -406,7 +405,6 @@ Please update the code to use this LORIS standard declaration:
406405
407406
```
408407
$factory = NDB_Factory::singleton();
409-
$DB = $factory->database();
410408
$config = $factory->config();
411409
$user = $factory->user();
412410
```
@@ -526,20 +524,11 @@ protected function setUp(): void \
526524
$this->_factory->setTesting(false);
527525

528526
$this->_configMock = $this->_factory->Config(CONFIG_XML);
529-
$database = $this->_configMock->getSetting('database');
530-
531-
$this->_dbMock = \Database::singleton(
532-
$database['database'],
533-
$database['username'],
534-
$database['password'],
535-
$database['host'],
536-
true
537-
);
538-
527+
$this->_dbMock = $this->_factory->database();
539528
}
540529

541530
```
542-
As you can see, the `$this->_dbMock` object is now declared as a `\Database::singleton()` rather than as a mock object. This is why it is not a pure unit test and why we cannot use the same “expects” method as in the previous section.
531+
As you can see, the `$this->_dbMock` object is now declared as a Database object rather than as a mock object. This is why it is not a pure unit test and why we cannot use the same “expects” method as in the previous section.
543532
544533
545534
The **tearDown** function is the same as before:

php/libraries/Database.class.inc

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ class Database implements LoggerAwareInterface
1818
use \Psr\Log\LoggerAwareTrait;
1919

2020
/**
21-
* The database handle, created by the connect() method
21+
* The database handle, created by the connect() method.
22+
* This *MUST* be private to ensure close deletes the
23+
* last (only) reference to it, which is how connections
24+
* are closed in PDO
2225
*
23-
* @var PDO
26+
* @var ?PDO
2427
* @access private
2528
*/
2629
var $_PDO = null;
@@ -56,7 +59,8 @@ class Database implements LoggerAwareInterface
5659

5760
/**
5861
* Constructor must be public for unit tests to pass, but this should not
59-
* be used directly. Should only be called by singleton method.
62+
* be used directly. References should only be retrieved via the LorisInstance
63+
* object.
6064
*/
6165
public function __construct()
6266
{
@@ -65,6 +69,7 @@ class Database implements LoggerAwareInterface
6569
// NDB_Factory will set an appropriate ApacheLogger after the fact.
6670
$this->setLogger(new \Psr\Log\NullLogger);
6771
}
72+
6873
/**
6974
* Singleton design pattern implementation - creates the object
7075
* and also connects to the database if necessary.
@@ -81,53 +86,23 @@ class Database implements LoggerAwareInterface
8186
* @param bool $trackChanges boolean determining if changes should be
8287
* logged to the history table
8388
*
84-
* @return \Database
85-
* @throws \DatabaseException
86-
* @access public
89+
* @return \Database
90+
* @throws \DatabaseException
91+
* @access public
92+
* @deprecated
8793
*/
88-
static function &singleton(
94+
static function singleton(
8995
string $database,
9096
bool $trackChanges = true
9197
): \Database {
92-
$username = getenv("LORIS_{$database}_USERNAME");
93-
$password = getenv("LORIS_{$database}_PASSWORD");
94-
$host = getenv("LORIS_{$database}_HOST");
95-
96-
static $connections = [];
97-
98-
// if no arguments, try to get the first connection or choke
99-
if (empty($database)
100-
&& empty($username)
101-
&& empty($password)
102-
&& empty($host)
103-
) {
104-
if (!empty($connections)) {
105-
reset($connections);
106-
$connection = current($connections);
107-
return $connection;
108-
} else {
109-
throw new DatabaseException("No database connection exists");
110-
}
111-
} else {
112-
// name the connection.
113-
if ($trackChanges) {
114-
$connection = md5($database.$host.$username.$password.'yep');
115-
} else {
116-
$connection = md5($database.$host.$username.$password.'nope');
117-
}
118-
119-
// create the connection if none exists
120-
if (empty($connections[$connection])) {
121-
$connections[$connection] = new Database();
122-
$connections[$connection]->connect(
123-
$database,
124-
$trackChanges
125-
);
126-
}
127-
128-
// return the connection
129-
return $connections[$connection];
130-
}
98+
// We don't have access to $this->logger since it's a static function,
99+
// so we need to use error_log() instead of $this->logger->warning().
100+
error_log(
101+
"Database::singleton is deprecated. "
102+
. "Use the getDatabaseConnection method from your LorisInstance"
103+
. " object instead."
104+
);
105+
return \NDB_Factory::singleton()->database();
131106
}
132107

133108
/**
@@ -1616,4 +1591,18 @@ class Database implements LoggerAwareInterface
16161591
{
16171592
return $this->_getServerVariable('version_comment');
16181593
}
1594+
1595+
/**
1596+
* Close the database connection.
1597+
*
1598+
* @return void
1599+
*/
1600+
public function closeConnection(): void
1601+
{
1602+
$this->_preparedStoreHistory = null;
1603+
1604+
$this->_PDO = null;
1605+
$this->_HistoryPDO = null;
1606+
1607+
}
16191608
}

php/libraries/NDB_Client.class.inc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,6 @@ class NDB_Client
5353

5454
$config = $factory->config($configFile);
5555

56-
// The factory uses the Settings object to call Database::singleton().
57-
// This is required so that further calls to Database::singleton()
58-
// will work.
5956
$DB = $factory->database();
6057

6158
// Register S3 stream wrapper if configured

php/libraries/NDB_Factory.class.inc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,10 @@ class NDB_Factory
173173
putenv("LORIS_{$dbname}_PASSWORD=" . $settings->dbPassword());
174174
putenv("LORIS_{$dbname}_HOST=" . $settings->dbHost());
175175

176-
$db_ref = \Database::singleton(
176+
$db_ref = new Database();
177+
$db_ref->connect(
177178
$settings->dbName(),
178-
true
179+
true,
179180
);
180181

181182
// Unset the variables now that they're no longer needed.

test/unittests/Database_Test.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,7 @@ protected function setUp(): void
9292
$this->factory = NDB_Factory::singleton();
9393
$this->factory->reset();
9494
$this->config = $this->factory->Config(CONFIG_XML);
95-
$database = $this->config->getSetting('database');
96-
$this->DB = $this->factory->database(
97-
$database['database'],
98-
$database['username'],
99-
$database['password'],
100-
$database['host'],
101-
true,
102-
);
103-
104-
$this->factory->setDatabase($this->DB);
105-
$this->factory->setConfig($this->config);
95+
$this->DB = $this->factory->database();
10696
}
10797

10898
/**

test/unittests/Loris_PHPUnit_Database_TestCase.php

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,22 +82,6 @@ protected function setUp(): void
8282
*/
8383
protected function createLorisDBConnection()
8484
{
85-
$dbname = $this->factory->settings()->dbName();
86-
putenv(
87-
"LORIS_{$dbname}_USERNAME="
88-
. $this->factory->settings()->dbUserName()
89-
);
90-
putenv(
91-
"LORIS_{$dbname}_PASSWORD="
92-
. $this->factory->settings()->dbPassword()
93-
);
94-
putenv(
95-
"LORIS_{$dbname}_HOST="
96-
. $this->factory->settings()->dbHost()
97-
);
98-
$this->database = Database::singleton(
99-
$this->factory->settings()->dbName(),
100-
);
85+
$this->database = $this->factory->database();
10186
}
102-
10387
}

test/unittests/UserTest.php

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -260,17 +260,7 @@ class UserTest extends TestCase
260260
* @var \Database | PHPUnit\Framework\MockObject\MockObject
261261
*/
262262
private $_mockDB;
263-
/**
264-
* Test double for Database object for hasLoggedIn method
265-
*
266-
* @note This is needed for User::hasLoggedIn because it declares and uses
267-
* the database differently than other methods in the User class.
268-
* This can be changed when the rest of the User class updates how it
269-
* declares its database. - Alexandra Livadas
270-
*
271-
* @var NDB_Factory
272-
*/
273-
private $_mockFactory;
263+
274264
/**
275265
* Maps config names to values
276266
* Used to set behaviour of NDB_Config test double
@@ -289,27 +279,16 @@ protected function setUp(): void
289279
$this->_factory = \NDB_Factory::singleton();
290280
$this->_factory->reset();
291281
$this->_configMock = $this->_factory->Config(CONFIG_XML);
292-
$database = $this->_configMock->getSetting('database');
293-
$this->_dbMock = $this->_factory->database(
294-
$database['database'],
295-
$database['username'],
296-
$database['password'],
297-
$database['host'],
298-
true
299-
);
282+
$this->_dbMock = $this->_factory->database();
300283

301284
$mockconfig = $this->getMockBuilder('NDB_Config')->getMock();
302285
$mockdb = $this->getMockBuilder('Database')->getMock();
303286

304287
'@phan-var \Database $mockdb';
305288
'@phan-var \NDB_Config $mockconfig';
306289

307-
$this->_mockDB = $mockdb;
308-
$this->_mockConfig = $mockconfig;
309-
$this->_mockFactory = \NDB_Factory::singleton();
310-
$this->_mockFactory->setDatabase($this->_dbMock);
311-
312-
$this->_factory->setConfig($this->_mockConfig);
290+
$this->_mockDB = $mockdb;
291+
$this->_mockConfig = $mockconfig;
313292

314293
$this->_userInfoComplete = $this->_userInfo;
315294
$this->_userInfoComplete['ID'] = '1';
@@ -332,6 +311,7 @@ protected function setUp(): void
332311
$this->_userInfo['Password_hash'] = $passwordHash;
333312
$this->_userInfoComplete['Password_hash'] = $passwordHash;
334313

314+
$this->_setUpTestDoublesForFactoryUser();
335315
$this->_user = \User::factory(self::USERNAME);
336316
}
337317

@@ -344,6 +324,7 @@ protected function setUp(): void
344324
protected function tearDown(): void
345325
{
346326
parent::tearDown();
327+
$this->_factory->database()->closeConnection();
347328
$this->_factory->reset();
348329
}
349330

@@ -359,7 +340,6 @@ protected function tearDown(): void
359340
*/
360341
public function testFactoryRetrievesUserInfo()
361342
{
362-
$this->_setUpTestDoublesForFactoryUser();
363343
$this->_user = \User::factory(self::USERNAME);
364344
//validate _user Info
365345
$this->assertEquals($this->_userInfoComplete, $this->_user->getData());
@@ -642,6 +622,7 @@ public function testInsert()
642622
$newUserInfo = $this->_userInfo;
643623
$newUserInfo['ID'] = 2;
644624
$newUserInfo['UserID'] = '968776';
625+
$newUserInfo['Email'] = 'notjohn.doe@mcgill.ca';
645626
\User::insert($newUserInfo);
646627
$this->_otherUser = \User::factory('968776');
647628
$this->assertEquals('968776', $this->_otherUser->getUsername());
@@ -655,6 +636,14 @@ public function testInsert()
655636
*/
656637
public function testUpdate()
657638
{
639+
// Insert the user so that it can be updated.
640+
$newUserInfo = $this->_userInfo;
641+
$newUserInfo['ID'] = 2;
642+
$newUserInfo['UserID'] = '968776';
643+
$newUserInfo['Email'] = 'notjohn.doe@mcgill.ca';
644+
\User::insert($newUserInfo);
645+
646+
// Test the update.
658647
$this->_otherUser = \User::factory('968776');
659648
$newInfo = ['ID' => '3'];
660649
$this->_otherUser->update($newInfo);
@@ -678,6 +667,9 @@ public function testUpdatePasswordWithExpiration()
678667

679668
// Cause usePwnedPasswordsAPI config option to return false.
680669
$mockConfig = &$this->_mockConfig;
670+
671+
$this->_factory->setConfig($mockConfig);
672+
681673
'@phan-var \PHPUnit\Framework\MockObject\MockObject $mockConfig';
682674
$mockConfig->expects($this->any())
683675
->method('settingEnabled')
@@ -714,6 +706,9 @@ public function testUpdatePasswordWithoutExpiry()
714706
->method('settingEnabled')
715707
->willReturn(false);
716708

709+
$mockConfig = &$this->_mockConfig;
710+
$this->_factory->setConfig($mockConfig);
711+
717712
$this->_user->updatePassword(
718713
new \Password(\Utility::randomString(16))
719714
);

test/unittests/VisitTest.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ protected function setUp(): void
6161
putenv("LORIS_{$database['database']}_PASSWORD={$database['password']}");
6262
putenv("LORIS_{$database['database']}_HOST={$database['host']}");
6363

64-
$this->DB = \Database::singleton(
65-
$database['database'],
66-
true,
67-
);
64+
$this->DB = $this->factory->database();
65+
6866
$this->visitController = new \Loris\VisitController($this->DB);
6967

7068
$v1 = new \Loris\Visit('V1');

0 commit comments

Comments
 (0)