-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPDOHelper.php
executable file
·343 lines (294 loc) · 12.7 KB
/
PDOHelper.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<?php
namespace TestPlugin {
use TestPlugin\PDOConnectionInfo;
use TestPlugin\SQLLoader;
use TestPlugin\DBResult;
use PDO;
/**
* Class PDOHelper A class to be used with PDO, to include comon functionality and make certain calls easy
* @package TestPlugin
*/
class PDOHelper {
private $info;
private $pdo = NULL;
private $sqlLoader;
/**
* Get the SQLLoader object that is associated with the database. This is commonly done because a database could have common table prefixes or other settings.
*
* @return \TestPlugin\SQLLoader The SQLLoader associated with this database
*/
public function getSqlLoader():SQLLoader {return $this->sqlLoader;}
private static $isDebug = false;
public static function enableDebug() {PDOHelper::$isDebug = true;}
public static function disableDebug() {PDOHelper::$isDebug = false;}
public static function isDebug():bool {return PDOHelper::$isDebug;}
public function __construct(PDOConnectionInfo $info, SQLLoader $loader) {
$this->info = $info;
$this->sqlLoader = $loader;
}
/**
* Get the database information
*
* @return string The database name and host
*/
public function getDBEndpoint():string {
return $this->info->database."@".$this->info->host;
}
public function connect() {
if(isset($this->info) && !empty($this->info) && $this->info->verify()) {
//default to these options
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_AUTOCOMMIT => true
];
$dsn = $this->info->makeMySQL_DSN();
try {
$this->pdo = new \PDO($dsn, $this->info->user, $this->info->pass, $options);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
} else {
throw new \PDOException("Unable to connect to database \"{$this->info->database}\" at \"{$this->info->host}\".", 500);
}
}
public function isConnected():bool {
try {
if(is_null($this->pdo)) {
$this->connect();
}
return ((bool) $this->pdo->query('SELECT 1+1') );
} catch (\PDOException $e) {
return false;
}
}
public function closeConnection(){
$this->pdo = NULL;
}
public function getPDOConnection():?\PDO {
if(isset($this->pdo) && !empty($this->pdo)) {
return $this->pdo;
} else {
return NULL;
}
}
public function disableForeignKeyChecks() {
try {
if(!is_null($this->pdo)){
$this->pdo->query('SET FOREIGN_KEY_CHECKS = 0;');
return true;
} else {
return false;
}
} catch (\PDOException $e) {
return false;
}
}
public function enableForeignKeyChecks() {
try {
if(!is_null($this->pdo)){
$this->pdo->query('SET FOREIGN_KEY_CHECKS = 1;');
return true;
} else {
return false;
}
} catch (\PDOException $e) {
return false;
}
}
public function setEmulatePrepares(bool $val) {
$this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, $val);
}
public function setAutoCommit(bool $val) {
$this->pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, $val);
}
public function tableExists($tableName):bool {
try {
if(is_null($this->pdo)) {
return false;
}
$sql = 'SELECT 1
FROM information_schema.tables
WHERE table_schema=?
AND table_name=?
LIMIT 1;';
$prepared = $this->pdo->prepare($sql);
return $prepared->execute([$this->info->database, $tableName]);
} catch (\PDOException $e) {
return false;
}
}
public function getAllTables():array {
try {
if(is_null($this->pdo)) {
return [];
}
$sql = 'SHOW TABLES LIKE \'%s\';';
$prepared = $this->pdo->prepare($sql);
return $prepared->execute();
} catch (\PDOException $e) {
return [];
}
}
public function beginTransaction():bool{
return $this->pdo->beginTransaction();
}
public function commitTransaction():bool{
return $this->pdo->commit();
}
public function rollbackTransaction():bool{
return $this->pdo->rollback();
}
/**
* Executes a group of SQL statements against this PDO connected database
*
* @param array $sqlArray The array of SQL statements to execute
* @param array $params The parameters for the SQL statements (will be passed to each)
* @param bool $stopOnError Whether to stop when an exception occurs in one statement
* @param bool $useTransaction Whether to use a transaction on these statements (could be false because this data is idempotent or you want to avoid table locks)
* @param bool $allAtOnce Whether to execute the SQL in one prepare call or not (in case the statements are dependent on each other, such as with aliases or the like)
*
* @return DBResult The database result of the SQL executions, or error messages
*/
public function executeAllSqlInArray(array $sqlArray, array $params = [], bool $stopOnError = false, $useTransaction = true, $allAtOnce=false): DBResult {
if(PDOHelper::isDebug()){
error_log("----------------------------- executeAllSqlInArray -----------------------------");
}
$result = new DBResult(true);
if(empty($sqlArray)) {
return $result;
}
if(!$this->isConnected()) {
$this->connect();
}
$i = 0;
if(!$allAtOnce) {
if($useTransaction) {
$this->setAutoCommit(false);
$this->beginTransaction();
}
foreach ($sqlArray as $sql) {
if (empty($sql)) {
continue;
}
$sqlParams = (!empty($params)) ? $params[$i] : [];
$sqlResult = $this->executeSQL($sql, $sqlParams, !$useTransaction);
$result->combine($sqlResult);
if ($stopOnError && !empty($sqlErrors)) {
break;
}
$i++;
}//end of foreach
if($useTransaction) {
if(PDOHelper::isDebug()){
error_log("Using Transaction");
}
if (empty($errors)) {
$this->commitTransaction();
if(PDOHelper::isDebug()) {
error_log('Commit transaction');
}
} else {
$this->rollbackTransaction();
if(PDOHelper::isDebug()) {
error_log('Rollback transaction');
}
}
$this->setAutoCommit(true);
}
} else {
//combine SQL, then execute all at once
$sql = implode('', $sqlArray);
if (empty($sql)) {
return $result;
}
$this->setEmulatePrepares(true);
//combine all params into one array
$sqlParams = (!empty($params)) ? array_merge(... $params) : [];
$sqlResult = $this->executeSQL($sql, $sqlParams, $useTransaction);
$result->combine($sqlResult);
$this->setEmulatePrepares(false);
}
return $result;
}
/**
* Executes a SQL statement against this PDO connected database
*
* @param string $sql The SQL statement to execute
* @param array $params The parameters for this SQL statement
* @param bool $useTransaction Whether to use a transaction on this statement (could be false because this data is idempotent or you want to avoid table locks)
*
* @return DBResult The database result of the SQL execution, or error messages
*/
public function executeSQL(string $sql, array $params = [], $useTransaction = true): DBResult {
if(PDOHelper::isDebug()){
error_log("----------------------------- executeSQL -----------------------------");
}
$result = new DBResult(true);
try {
if($useTransaction) {
$this->setAutoCommit(false);
$this->beginTransaction();
}
$prepared = $this->pdo->prepare($sql);
$success = false;
if(empty($params)) {
$success = $prepared->execute();
} else {
$success = $prepared->execute($params);
}
$result->result = $prepared->rowCount();
$prepared->closeCursor();
if(!$success) {
$result->success = false;
$result->messages[] = "The following SQL encountered an error: [".$sql."]: ".$prepared->errorInfo();
}
if($useTransaction) {
if ($success) {
$this->commitTransaction();
} else {
$this->rollbackTransaction();
}
$this->setAutoCommit(true);
}
} catch (\PDOException $e) {
if($useTransaction) {
$this->rollbackTransaction();
$this->setAutoCommit(true);
}
$result->success = false;
$result->messages[] = "The following SQL encountered an error: [".$sql."](".$e->getCode()."): ".$e->getMessage();
}
return $result;
}
/**
* Resets a database -- dropping all tables and data or use an optional SQL to reset a given database (such as setting the DB to a "vanilla" state of tables/data)
*
* @param string $alternateResetSQL The alternate SQL statement to use when resetting the database (otherwise a default one which drops all tables will be used)
*
* @return DBResult The database result of the database reset, or error messages
*/
public function ResetDB($alternateResetSQL = ""):DBResult {
$sqlToUse = 'reset_db';
if(!empty($alternateResetSQL) ) {
$sqlToUse = $alternateResetSQL;
}
$resetSQL = $this->sqlLoader->getSqlFileStatements($sqlToUse);
return $this->executeAllSqlInArray($resetSQL, [], false, true, true);
}
/**
* Resets a database -- dropping all tables and data or use an optional SQL to reset a given database (such as setting the DB to a "vanilla" state of tables/data)
*
* @param array $setupSQL The SQL statements to use when setting up the database
*
* @return DBResult The database result of the database setup, or error messages
*/
public function SetupDB(array $setupSQL):DBResult {
$this->disableForeignKeyChecks();
$result = $this->executeAllSqlInArray($setupSQL);
$this->enableForeignKeyChecks();
return $result;
}
}
}