|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace SilverStripe\ORM\Connect; |
|
4
|
|
|
|
|
5
|
|
|
use SilverStripe\Core\ClassInfo; |
|
6
|
|
|
use SilverStripe\Core\Injector\Injectable; |
|
7
|
|
|
use SilverStripe\Core\Injector\Injector; |
|
8
|
|
|
use SilverStripe\Dev\TestOnly; |
|
9
|
|
|
use SilverStripe\ORM\DataExtension; |
|
10
|
|
|
use SilverStripe\ORM\DataObject; |
|
11
|
|
|
use SilverStripe\ORM\DB; |
|
12
|
|
|
|
|
13
|
|
|
class TempDatabase |
|
14
|
|
|
{ |
|
15
|
|
|
use Injectable; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* Connection name |
|
19
|
|
|
* |
|
20
|
|
|
* @var string |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $name = null; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* Create a new temp database |
|
26
|
|
|
* |
|
27
|
|
|
* @param string $name DB Connection name to use |
|
28
|
|
|
*/ |
|
29
|
|
|
public function __construct($name = 'default') |
|
30
|
|
|
{ |
|
31
|
|
|
$this->name = $name; |
|
32
|
|
|
} |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* Check if the given name matches the temp_db pattern |
|
36
|
|
|
* |
|
37
|
|
|
* @param string $name |
|
38
|
|
|
* @return bool |
|
39
|
|
|
*/ |
|
40
|
|
|
protected function isDBTemp($name) |
|
41
|
|
|
{ |
|
42
|
|
|
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; |
|
43
|
|
|
$result = preg_match( |
|
44
|
|
|
sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), |
|
45
|
|
|
$name |
|
46
|
|
|
); |
|
47
|
|
|
return $result === 1; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @return Database |
|
52
|
|
|
*/ |
|
53
|
|
|
protected function getConn() |
|
54
|
|
|
{ |
|
55
|
|
|
return DB::get_conn($this->name); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Returns true if we are currently using a temporary database |
|
60
|
|
|
* |
|
61
|
|
|
* @return bool |
|
62
|
|
|
*/ |
|
63
|
|
|
public function isUsed() |
|
64
|
|
|
{ |
|
65
|
|
|
$selected = $this->getConn()->getSelectedDatabase(); |
|
66
|
|
|
return $this->isDBTemp($selected); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Destroy the current temp database |
|
71
|
|
|
*/ |
|
72
|
|
|
public function kill() |
|
73
|
|
|
{ |
|
74
|
|
|
// Delete our temporary database |
|
75
|
|
|
if (!$this->isUsed()) { |
|
76
|
|
|
return; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
// Check the database actually exists |
|
80
|
|
|
$dbConn = $this->getConn(); |
|
81
|
|
|
$dbName = $dbConn->getSelectedDatabase(); |
|
82
|
|
|
if (!$dbConn->databaseExists($dbName)) { |
|
83
|
|
|
return; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
// Some DataExtensions keep a static cache of information that needs to |
|
87
|
|
|
// be reset whenever the database is killed |
|
88
|
|
|
foreach (ClassInfo::subclassesFor(DataExtension::class) as $class) { |
|
89
|
|
|
$toCall = array($class, 'on_db_reset'); |
|
90
|
|
|
if (is_callable($toCall)) { |
|
91
|
|
|
call_user_func($toCall); |
|
92
|
|
|
} |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
// echo "Deleted temp database " . $dbConn->currentDatabase() . "\n"; |
|
|
|
|
|
|
96
|
|
|
$dbConn->dropSelectedDatabase(); |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* Remove all content from the temporary database. |
|
101
|
|
|
*/ |
|
102
|
|
|
public function clearAllData() |
|
103
|
|
|
{ |
|
104
|
|
|
if (!$this->isUsed()) { |
|
105
|
|
|
return; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
$this->getConn()->clearAllData(); |
|
109
|
|
|
|
|
110
|
|
|
// Some DataExtensions keep a static cache of information that needs to |
|
111
|
|
|
// be reset whenever the database is cleaned out |
|
112
|
|
|
$classes = array_merge( |
|
113
|
|
|
ClassInfo::subclassesFor(DataExtension::class), |
|
114
|
|
|
ClassInfo::subclassesFor(DataObject::class) |
|
115
|
|
|
); |
|
116
|
|
|
foreach ($classes as $class) { |
|
117
|
|
|
$toCall = array($class, 'on_db_reset'); |
|
118
|
|
|
if (is_callable($toCall)) { |
|
119
|
|
|
call_user_func($toCall); |
|
120
|
|
|
} |
|
121
|
|
|
} |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
|
|
* Create temp DB without creating extra objects |
|
126
|
|
|
* |
|
127
|
|
|
* @return string DB name |
|
128
|
|
|
*/ |
|
129
|
|
|
public function build() |
|
130
|
|
|
{ |
|
131
|
|
|
// Disable PHPUnit error handling |
|
132
|
|
|
$oldErrorHandler = set_error_handler(null); |
|
133
|
|
|
|
|
134
|
|
|
// Create a temporary database, and force the connection to use UTC for time |
|
135
|
|
|
$dbConn = $this->getConn(); |
|
136
|
|
|
$prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_'; |
|
137
|
|
|
do { |
|
138
|
|
|
$dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999))); |
|
139
|
|
|
} while ($dbConn->databaseExists($dbname)); |
|
140
|
|
|
|
|
141
|
|
|
$dbConn->selectDatabase($dbname, true); |
|
142
|
|
|
|
|
143
|
|
|
$this->resetDBSchema(); |
|
144
|
|
|
|
|
145
|
|
|
// Reinstate PHPUnit error handling |
|
146
|
|
|
set_error_handler($oldErrorHandler); |
|
147
|
|
|
|
|
148
|
|
|
// Ensure test db is killed on exit |
|
149
|
|
|
register_shutdown_function(function () { |
|
150
|
|
|
$this->kill(); |
|
151
|
|
|
}); |
|
152
|
|
|
|
|
153
|
|
|
return $dbname; |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
/** |
|
157
|
|
|
* Clear all temp DBs on this connection |
|
158
|
|
|
* |
|
159
|
|
|
* Note: This will output results to stdout unless suppressOutput |
|
160
|
|
|
* is set on the current db schema |
|
161
|
|
|
*/ |
|
162
|
|
|
public function deleteAll() |
|
163
|
|
|
{ |
|
164
|
|
|
$schema = $this->getConn()->getSchemaManager(); |
|
165
|
|
|
foreach ($schema->databaseList() as $dbName) { |
|
166
|
|
|
if ($this->isDBTemp($dbName)) { |
|
167
|
|
|
$schema->dropDatabase($dbName); |
|
168
|
|
|
$schema->alterationMessage("Dropped database \"$dbName\"", 'deleted'); |
|
169
|
|
|
flush(); |
|
170
|
|
|
} |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
/** |
|
175
|
|
|
* Reset the testing database's schema. |
|
176
|
|
|
* |
|
177
|
|
|
* @param array $extraDataObjects List of extra dataobjects to build |
|
178
|
|
|
*/ |
|
179
|
|
|
public function resetDBSchema(array $extraDataObjects = []) |
|
180
|
|
|
{ |
|
181
|
|
|
if (!$this->isUsed()) { |
|
182
|
|
|
return; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
DataObject::reset(); |
|
186
|
|
|
|
|
187
|
|
|
// clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild() |
|
188
|
|
|
Injector::inst()->unregisterObjects(DataObject::class); |
|
189
|
|
|
|
|
190
|
|
|
$dataClasses = ClassInfo::subclassesFor(DataObject::class); |
|
191
|
|
|
array_shift($dataClasses); |
|
192
|
|
|
|
|
193
|
|
|
$schema = $this->getConn()->getSchemaManager(); |
|
194
|
|
|
$schema->quiet(); |
|
195
|
|
|
$schema->schemaUpdate(function () use ($dataClasses, $extraDataObjects) { |
|
196
|
|
|
foreach ($dataClasses as $dataClass) { |
|
197
|
|
|
// Check if class exists before trying to instantiate - this sidesteps any manifest weirdness |
|
198
|
|
|
if (class_exists($dataClass)) { |
|
199
|
|
|
$SNG = singleton($dataClass); |
|
200
|
|
|
if (!($SNG instanceof TestOnly)) { |
|
201
|
|
|
$SNG->requireTable(); |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
// If we have additional dataobjects which need schema, do so here: |
|
207
|
|
|
if ($extraDataObjects) { |
|
|
|
|
|
|
208
|
|
|
foreach ($extraDataObjects as $dataClass) { |
|
209
|
|
|
$SNG = singleton($dataClass); |
|
210
|
|
|
if (singleton($dataClass) instanceof DataObject) { |
|
211
|
|
|
$SNG->requireTable(); |
|
212
|
|
|
} |
|
213
|
|
|
} |
|
214
|
|
|
} |
|
215
|
|
|
}); |
|
216
|
|
|
|
|
217
|
|
|
ClassInfo::reset_db_cache(); |
|
218
|
|
|
DataObject::singleton()->flushCache(); |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.