-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmysql_wrapper.class.php
352 lines (306 loc) · 8.87 KB
/
mysql_wrapper.class.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
344
345
346
347
348
349
350
351
352
<?php
/*
* Singleton class - rather than globalising the object instance you should call
* $var = MYSQL_WRAPPER::get_singleton() when it's needed but not accessible.
*
* Strings 'NOW()' and 'NULL' will be converted to MySQL keywords when found in INSERT and UPDATE queries
*/
class MYSQL_WRAPPER {
private $dsn = NULL;
private $dbh = NULL;
private $log = array();
private $log_errors = FALSE;
private $log_queries = FALSE;
private $num_queries = 0;
private static $instance = NULL;
/*
* !CLASS FUNCTIONALITY
*/
public function __construct() {
}
private function __clone() {
}
public static function get_singleton() {
if (self::$instance == NULL) {
self::$instance = new MYSQL_WRAPPER();
}
return self::$instance;
}
/*
* !CONNECTION
*
* $settings is an array containing the following key/value pairs:
*
* [host] => hostname or ip address
* [port] => port number (defaults to 3306)
* or
* [socket] => path to mysql socket
*
* [username] => mysql username (required)
* [password] => mysql password (required)
* [database] => mysql database (required)
*
* [charset] => chartset (defaults to utf8)
*/
function connect($settings = FALSE) {
try {
// build dsn
$this->dsn = 'mysql:';
$options = array();
// host address (address or socket)
if (isset($settings['socket']) && !empty($settings['socket'])) {
$this->dsn .= 'unix_socket=' . $settings['socket'];
} else {
$this->dsn .= 'host=' . $settings['host'];
if (isset($settings['port']) && !empty($settings['port'])) {
$this->dsn .= ';port=' . $settings['port'];
}
}
// database name
$this->dsn .= ';dbname=' . $settings['database'];
// charset (default to utf8)
if (isset($settings['charset']) && !empty($settings['charset'])) {
$this->dsn .= ';charset=' . $settings['charset'];
$options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $settings['charset'];
} else {
$this->dsn .= ';charset=utf8';
$options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8';
}
$this->dbh = new PDO($this->dsn, $settings['username'], $settings['password'], $options);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Cannot connect to " . $this->dsn . "\n";
echo $e->getMessage() . "\n";
exit();
}
}
/*
* !CORE QUERIES
*
* TO DO: Restrict runctions to expected queries
* INSERT ... ON DUPLICATE UPDATE
* DELETE
*/
/*
* Used by all querying functions to prepare and execute the statement, though it can be
* called directly.
*/
function query($sql, $vars) {
$sth = $this->dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
if (is_array($vars) && sizeof($vars)) {
$result = $sth->execute($vars);
} else {
$result = $sth->execute();
}
$this->num_queries++;
if ($this->log_queries) {
$this->log_append($sth->queryString);
}
// must have errored (note that most errors will be caught in dbh->prepare and will cause an exception so this is only for edge cases)
if (!$result) {
$error = $sth->errorInfo();
$this->record_error($sth->queryString, $vars, $error[2]);
}
return $sth;
}
/*
* Runs standard SELECT queries and returns an associative array containing all results.
*/
function select($sql, $vars = array()) {
$sth = $this->query($sql, $vars);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
/*
* Returns a single result as an associate array. Users are expected to write 'LIMIT 1' in there
* own queries as best practice but if not this function will add it to save DB load.
*/
function select_single($sql, $vars = array()) {
if (!preg_match('/limit\s1/i', substr(trim($sql), -7))) {
$sql .= ' LIMIT 1';
}
$sth = $this->query($sql, $vars);
return $sth->fetch(PDO::FETCH_ASSOC);
}
/*
* Provide table name and associate array of field/value pairs
* Returns inserted ID or FALSE on failure
*/
function insert($table, $insert_fields, $ignore = FALSE) {
if (sizeof($insert_fields)) {
$vars = array();
$sql = "INSERT ";
if ($ignore === TRUE) {
$sql .= "IGNORE ";
}
$sql .= "INTO `$table` SET ";
foreach ($insert_fields as $k=>$v) {
// treat certain MySQL functions as safe
if ($v === 'NOW()') {
$sql .= "`$k` = NOW(), ";
} else if ($v === 'NULL') {
$sql .= "`$k` = NULL, ";
} else {
$sql .= "`$k` = :$k, ";
$vars[":$k"] = $v;
}
}
$sql = substr($sql, 0, -2);
if ($sth = $this->query($sql, $vars)) {
return $this->dbh->lastInsertId();
}
}
return FALSE;
}
/*
* Provide table name and associate array of field/value pairs
* Enforces a where clause for protection (practically, it's always wanted anyway)
* Where clause fields are auto-prepended with __where__ to prevent conflicts with the update fields
* Returns FALSE on failure
*/
function update($table, $update_fields, $where_clause = false, $where_fields = false) {
if ($where_clause && sizeof($update_fields)) {
$vars = array();
$sql = "UPDATE `$table` SET ";
foreach ($update_fields as $k=>$v) {
// treat certain MySQL functions as safe
if ($v === 'NOW()') {
$sql .= "`$k` = NOW(), ";
} else if ($v === 'NULL') {
$sql .= "`$k` = NULL, ";
} else {
$sql .= "`$k` = :$k, ";
$vars[":$k"] = $v;
}
}
$sql = substr($sql, 0, -2);
$sql .= " WHERE " . str_replace(':', ':__where__', $where_clause);
foreach ($where_fields as $k=>$v) {
$vars[":__where__$k"] = $v;
}
if ($sth = $this->query($sql, $vars)) {
return TRUE;
}
}
return FALSE;
}
/*
* Provide table name and associate array of field/value pairs
* Returns FALSE on failure
*/
function insert_or_update($table, $insert_fields, $update_fields) {
if (sizeof($insert_fields) && sizeof($update_fields)) {
$vars = array();
$sql = "INSERT INTO `$table` SET ";
foreach ($insert_fields as $k=>$v) {
// treat certain MySQL functions as safe
if ($v === 'NOW()') {
$sql .= "`$k` = NOW(), ";
} else if ($v === 'NULL') {
$sql .= "`$k` = NULL, ";
} else {
$sql .= "`$k` = :$k, ";
$vars[":$k"] = $v;
}
}
$sql = substr($sql, 0, -2);
$sql .= " ON DUPLICATE KEY UPDATE ";
foreach ($update_fields as $k=>$v) {
// treat certain MySQL functions as safe
if ($v === 'NOW()') {
$sql .= "`$k` = NOW(), ";
} else if ($v === 'NULL') {
$sql .= "`$k` = NULL, ";
} else {
$sql .= "`$k` = :$k, ";
$vars[":$k"] = $v;
}
}
$sql = substr($sql, 0, -2);
if ($sth = $this->query($sql, $vars)) {
return TRUE;
}
}
return FALSE;
}
/*
* Provide table name
* Enforces a where clause for protection (practically, it's always wanted anyway)
* Returns FALSE on failure
*/
function delete($table, $where_clause = false, $where_fields = array()) {
$vars = array();
$sql = "DELETE FROM `$table`";
if ($where_clause !== FALSE) {
$sql .= " WHERE " . $where_clause;
}
foreach ($where_fields as $k=>$v) {
$vars[":$k"] = $v;
}
if ($sth = $this->query($sql, $vars)) {
return TRUE;
}
return FALSE;
}
/*
* !UTILITIES
*
* Useful DB-related tools
*/
// ONLY to my used when a value can't supported above (E.g. WHERE field LIKE '%str%')
public function quote($str) {
return substr($this->dbh->quote($str), 1, -1);
}
// Convert a date from an dd/mm/yyyy input to yyyy-mm-dd
public function input_date_to_system_date($in) {
if (preg_match('/^\d{1,2}\/\d{1,2}\/\d{2,4}$/', $in)) {
$parts = explode('/', $in);
return str_pad($parts[2], 4, "20", STR_PAD_LEFT) . '-' . str_pad($parts[1], 2, "0", STR_PAD_LEFT) . '-' . str_pad($parts[0], 2, "0", STR_PAD_LEFT);
}
return FALSE;
}
// Convert a date from yyyy-mm-dd for dd/mm/yyyy input fields
public function system_date_to_input_date($in, $default_today = FALSE) {
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $in)) {
$parts = explode('-', $in);
return $parts[2] . '/' . $parts[1] . '/' . $parts[0];
} else if ($default_today === TRUE && empty($in)) {
return date("d/m/Y");
}
return '';
}
/*
* !LOGGING
*/
function log_append($line) {
$this->log[] = $line;
}
function log_fetch() {
return $this->log;
}
function log_reset() {
$this->log=array();
}
function num_queries() {
return $this->num_queries;
}
function record_error($sql, $vars, $msg) {
if ($this->log_errors === TRUE) {
if (is_array($vars)) {
$vars = serialize($vars);
}
$this->insert('error_log', array(
'thedate' => 'NOW()',
'query' => $sql,
'vars' => $vars,
'message' => $msg,
));
}
}
// When the singleton object is serialized/unserialized
// the value of static variable will be lost
// so you need to re-assign to the static $instance
// the object by using restore_instance member (setter function)
public function restore_instance($session_instance) {
self::$instance = $session_instance;
}
}