Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like MeekroDB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MeekroDB, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
133 | class MeekroDB { |
||
134 | // initial connection |
||
135 | public $dbName = ''; |
||
136 | public $user = ''; |
||
137 | public $password = ''; |
||
138 | public $host = 'localhost'; |
||
139 | public $port = null; |
||
140 | public $encoding = 'latin1'; |
||
141 | |||
142 | // configure workings |
||
143 | public $param_char = '%'; |
||
144 | public $named_param_separator = '_'; |
||
145 | public $success_handler = false; |
||
146 | public $error_handler = true; |
||
147 | public $throw_exception_on_error = false; |
||
148 | public $nonsql_error_handler = null; |
||
149 | public $throw_exception_on_nonsql_error = false; |
||
150 | public $nested_transactions = false; |
||
151 | public $usenull = true; |
||
152 | |||
153 | // internal |
||
154 | public $internal_mysql = null; |
||
155 | public $server_info = null; |
||
156 | public $insert_id = 0; |
||
157 | public $num_rows = 0; |
||
158 | public $affected_rows = 0; |
||
159 | public $current_db = null; |
||
160 | public $nested_transactions_count = 0; |
||
161 | |||
162 | |||
163 | /** |
||
164 | * @param string $host |
||
165 | * @param string $user |
||
166 | * @param string $password |
||
167 | * @param string $dbName |
||
168 | * @param string $port |
||
169 | * @param string $encoding |
||
170 | */ |
||
171 | public function __construct($host = null, $user = null, $password = null, $dbName = null, $port = null, $encoding = null) { |
||
172 | if ($host === null) { |
||
173 | $host = DB::$host; |
||
174 | } |
||
175 | if ($user === null) { |
||
176 | $user = DB::$user; |
||
177 | } |
||
178 | if ($password === null) { |
||
179 | $password = DB::$password; |
||
180 | } |
||
181 | if ($dbName === null) { |
||
182 | $dbName = DB::$dbName; |
||
183 | } |
||
184 | if ($port === null) { |
||
185 | $port = DB::$port; |
||
186 | } |
||
187 | if ($encoding === null) { |
||
188 | $encoding = DB::$encoding; |
||
189 | } |
||
190 | |||
191 | $this->host = $host; |
||
192 | $this->user = $user; |
||
193 | $this->password = $password; |
||
194 | $this->dbName = $dbName; |
||
195 | $this->port = $port; |
||
196 | $this->encoding = $encoding; |
||
197 | } |
||
198 | |||
199 | public function get() { |
||
200 | $mysql = $this->internal_mysql; |
||
201 | |||
202 | if (!($mysql instanceof MySQLi)) { |
||
203 | if (!$this->port) { |
||
204 | $this->port = ini_get('mysqli.default_port'); |
||
205 | } |
||
206 | $this->current_db = $this->dbName; |
||
207 | |||
208 | $mysql = new mysqli($this->host, $this->user, $this->password, $this->dbName, $this->port); |
||
209 | |||
210 | if ($mysql->connect_error) { |
||
211 | $this->nonSQLError('Unable to connect to MySQL server! Error: '.$mysql->connect_error); |
||
212 | } |
||
213 | |||
214 | $mysql->set_charset($this->encoding); |
||
215 | $this->internal_mysql = $mysql; |
||
216 | $this->server_info = $mysql->server_info; |
||
217 | } |
||
218 | |||
219 | return $mysql; |
||
220 | } |
||
221 | |||
222 | public function disconnect() { |
||
223 | $mysqli = $this->internal_mysql; |
||
224 | if ($mysqli instanceof MySQLi) { |
||
225 | if ($thread_id = $mysqli->thread_id) { |
||
226 | $mysqli->kill($thread_id); |
||
227 | } |
||
228 | $mysqli->close(); |
||
229 | } |
||
230 | $this->internal_mysql = null; |
||
231 | } |
||
232 | |||
233 | public function nonSQLError($message) { |
||
234 | if ($this->throw_exception_on_nonsql_error) { |
||
235 | $err = new MeekroDBException($message); |
||
236 | throw $err; |
||
237 | } |
||
238 | |||
239 | $error_handler = is_callable($this->nonsql_error_handler) ? $this->nonsql_error_handler : 'meekrodb_error_handler'; |
||
240 | |||
241 | call_user_func($error_handler, array( |
||
242 | 'type' => 'nonsql', |
||
243 | 'error' => $message |
||
244 | )); |
||
245 | } |
||
246 | |||
247 | public function debugMode($handler = true) { |
||
248 | $this->success_handler = $handler; |
||
249 | } |
||
250 | |||
251 | public function serverVersion() { $this->get(); return $this->server_info; } |
||
252 | public function transactionDepth() { return $this->nested_transactions_count; } |
||
253 | public function insertId() { return $this->insert_id; } |
||
254 | public function affectedRows() { return $this->affected_rows; } |
||
255 | public function count() { $args = func_get_args(); return call_user_func_array(array($this, 'numRows'), $args); } |
||
256 | public function numRows() { return $this->num_rows; } |
||
257 | |||
258 | public function useDB() { $args = func_get_args(); return call_user_func_array(array($this, 'setDB'), $args); } |
||
259 | public function setDB($dbName) { |
||
260 | $database = $this->get(); |
||
261 | if (!$database->select_db($dbName)) { |
||
262 | $this->nonSQLError("Unable to set database to $dbName"); |
||
263 | } |
||
264 | $this->current_db = $dbName; |
||
265 | } |
||
266 | |||
267 | |||
268 | public function startTransaction() { |
||
269 | if ($this->nested_transactions && $this->serverVersion() < '5.5') { |
||
270 | return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL ".$this->serverVersion()); |
||
271 | } |
||
272 | |||
273 | if (!$this->nested_transactions || $this->nested_transactions_count == 0) { |
||
274 | $this->query('START TRANSACTION'); |
||
275 | $this->nested_transactions_count = 1; |
||
276 | } else { |
||
277 | $this->query("SAVEPOINT LEVEL{$this->nested_transactions_count}"); |
||
278 | $this->nested_transactions_count++; |
||
279 | } |
||
280 | |||
281 | return $this->nested_transactions_count; |
||
282 | } |
||
283 | |||
284 | public function commit($all = false) { |
||
285 | if ($this->nested_transactions && $this->serverVersion() < '5.5') { |
||
286 | return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL ".$this->serverVersion()); |
||
287 | } |
||
288 | |||
289 | if ($this->nested_transactions && $this->nested_transactions_count > 0) { |
||
290 | $this->nested_transactions_count--; |
||
291 | } |
||
292 | |||
293 | if (!$this->nested_transactions || $all || $this->nested_transactions_count == 0) { |
||
294 | $this->nested_transactions_count = 0; |
||
295 | $this->query('COMMIT'); |
||
296 | } else { |
||
297 | $this->query("RELEASE SAVEPOINT LEVEL{$this->nested_transactions_count}"); |
||
298 | } |
||
299 | |||
300 | return $this->nested_transactions_count; |
||
301 | } |
||
302 | |||
303 | public function rollback($all = false) { |
||
304 | if ($this->nested_transactions && $this->serverVersion() < '5.5') { |
||
305 | return $this->nonSQLError("Nested transactions are only available on MySQL 5.5 and greater. You are using MySQL ".$this->serverVersion()); |
||
306 | } |
||
307 | |||
308 | if ($this->nested_transactions && $this->nested_transactions_count > 0) { |
||
309 | $this->nested_transactions_count--; |
||
310 | } |
||
311 | |||
312 | if (!$this->nested_transactions || $all || $this->nested_transactions_count == 0) { |
||
313 | $this->nested_transactions_count = 0; |
||
314 | $this->query('ROLLBACK'); |
||
315 | } else { |
||
316 | $this->query("ROLLBACK TO SAVEPOINT LEVEL{$this->nested_transactions_count}"); |
||
317 | } |
||
318 | |||
319 | return $this->nested_transactions_count; |
||
320 | } |
||
321 | |||
322 | protected function formatTableName($table) { |
||
323 | $table = trim($table, '`'); |
||
324 | |||
325 | if (strpos($table, '.')) { |
||
326 | return implode('.', array_map(array($this, 'formatTableName'), explode('.', $table))); |
||
327 | } else { |
||
328 | return '`'.str_replace('`', '``', $table).'`'; |
||
329 | } |
||
330 | } |
||
331 | |||
332 | public function update() { |
||
333 | $args = func_get_args(); |
||
334 | $table = array_shift($args); |
||
335 | $params = array_shift($args); |
||
336 | $where = array_shift($args); |
||
337 | |||
338 | $query = "UPDATE %b SET %? WHERE ".$where; |
||
339 | |||
340 | array_unshift($args, $params); |
||
341 | array_unshift($args, $table); |
||
342 | array_unshift($args, $query); |
||
343 | return call_user_func_array(array($this, 'query'), $args); |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * @param string $which |
||
348 | */ |
||
349 | public function insertOrReplace($which, $table, $datas, $options = array()) { |
||
350 | $datas = unserialize(serialize($datas)); // break references within array |
||
351 | $keys = $values = array(); |
||
352 | |||
353 | if (isset($datas[0]) && is_array($datas[0])) { |
||
354 | foreach ($datas as $datum) { |
||
355 | ksort($datum); |
||
356 | if (!$keys) { |
||
357 | $keys = array_keys($datum); |
||
358 | } |
||
359 | $values[] = array_values($datum); |
||
360 | } |
||
361 | |||
362 | } else { |
||
363 | $keys = array_keys($datas); |
||
364 | $values = array_values($datas); |
||
365 | } |
||
366 | |||
367 | if (isset($options['ignore']) && $options['ignore']) { |
||
368 | $which = 'INSERT IGNORE'; |
||
369 | } |
||
370 | |||
371 | if (isset($options['update']) && is_array($options['update']) && $options['update'] && strtolower($which) == 'insert') { |
||
372 | if (array_values($options['update']) !== $options['update']) { |
||
373 | return $this->query("INSERT INTO %b %lb VALUES %? ON DUPLICATE KEY UPDATE %?", $table, $keys, $values, $options['update']); |
||
374 | } else { |
||
375 | $update_str = array_shift($options['update']); |
||
376 | $query_param = array("INSERT INTO %b %lb VALUES %? ON DUPLICATE KEY UPDATE $update_str", $table, $keys, $values); |
||
377 | $query_param = array_merge($query_param, $options['update']); |
||
378 | return call_user_func_array(array($this, 'query'), $query_param); |
||
379 | } |
||
380 | |||
381 | } |
||
382 | |||
383 | return $this->query("%l INTO %b %lb VALUES %?", $which, $table, $keys, $values); |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * @param string $table |
||
388 | */ |
||
389 | public function insert($table, $data) { return $this->insertOrReplace('INSERT', $table, $data); } |
||
390 | public function insertIgnore($table, $data) { return $this->insertOrReplace('INSERT', $table, $data, array('ignore' => true)); } |
||
391 | public function replace($table, $data) { return $this->insertOrReplace('REPLACE', $table, $data); } |
||
392 | |||
393 | public function insertUpdate() { |
||
394 | $args = func_get_args(); |
||
395 | $table = array_shift($args); |
||
396 | $data = array_shift($args); |
||
397 | |||
398 | if (!isset($args[0])) { // update will have all the data of the insert |
||
399 | if (isset($data[0]) && is_array($data[0])) { //multiple insert rows specified -- failing! |
||
400 | $this->nonSQLError("Badly formatted insertUpdate() query -- you didn't specify the update component!"); |
||
401 | } |
||
402 | |||
403 | $args[0] = $data; |
||
404 | } |
||
405 | |||
406 | if (is_array($args[0])) { |
||
407 | $update = $args[0]; |
||
408 | } else { |
||
409 | $update = $args; |
||
410 | } |
||
411 | |||
412 | return $this->insertOrReplace('INSERT', $table, $data, array('update' => $update)); |
||
413 | } |
||
414 | |||
415 | public function delete() { |
||
416 | $args = func_get_args(); |
||
417 | $table = $this->formatTableName(array_shift($args)); |
||
418 | $where = array_shift($args); |
||
419 | $buildquery = "DELETE FROM $table WHERE $where"; |
||
420 | array_unshift($args, $buildquery); |
||
421 | return call_user_func_array(array($this, 'query'), $args); |
||
422 | } |
||
423 | |||
424 | public function sqleval() { |
||
425 | $args = func_get_args(); |
||
426 | $text = call_user_func_array(array($this, 'parseQueryParams'), $args); |
||
427 | return new MeekroDBEval($text); |
||
428 | } |
||
429 | |||
430 | public function columnList($table) { |
||
431 | return $this->queryOneColumn('Field', "SHOW COLUMNS FROM $table"); |
||
432 | } |
||
433 | |||
434 | public function tableList($database = null) { |
||
435 | if ($database) { |
||
436 | $olddb = $this->current_db; |
||
437 | $this->useDB($database); |
||
438 | } |
||
439 | |||
440 | $result = $this->queryFirstColumn('SHOW TABLES'); |
||
441 | if (isset($olddb)) { |
||
442 | $this->useDB($olddb); |
||
443 | } |
||
444 | return $result; |
||
445 | } |
||
446 | |||
447 | protected function preparseQueryParams() { |
||
448 | $args = func_get_args(); |
||
449 | $sql = trim(strval(array_shift($args))); |
||
450 | $args_all = $args; |
||
451 | |||
452 | if (count($args_all) == 0) { |
||
453 | return array($sql); |
||
454 | } |
||
455 | |||
456 | $param_char_length = strlen($this->param_char); |
||
457 | $named_seperator_length = strlen($this->named_param_separator); |
||
458 | |||
459 | $types = array( |
||
460 | $this->param_char.'ll', // list of literals |
||
461 | $this->param_char.'ls', // list of strings |
||
462 | $this->param_char.'l', // literal |
||
463 | $this->param_char.'li', // list of integers |
||
464 | $this->param_char.'ld', // list of decimals |
||
465 | $this->param_char.'lb', // list of backticks |
||
466 | $this->param_char.'lt', // list of timestamps |
||
467 | $this->param_char.'s', // string |
||
468 | $this->param_char.'i', // integer |
||
469 | $this->param_char.'d', // double / decimal |
||
470 | $this->param_char.'b', // backtick |
||
471 | $this->param_char.'t', // timestamp |
||
472 | $this->param_char.'?', // infer type |
||
473 | $this->param_char.'ss' // search string (like string, surrounded with %'s) |
||
474 | ); |
||
475 | |||
476 | // generate list of all MeekroDB variables in our query, and their position |
||
477 | // in the form "offset => variable", sorted by offsets |
||
478 | $posList = array(); |
||
479 | foreach ($types as $type) { |
||
480 | $lastPos = 0; |
||
481 | while (($pos = strpos($sql, $type, $lastPos)) !== false) { |
||
482 | $lastPos = $pos + 1; |
||
483 | if (isset($posList[$pos]) && strlen($posList[$pos]) > strlen($type)) { |
||
484 | continue; |
||
485 | } |
||
486 | $posList[$pos] = $type; |
||
487 | } |
||
488 | } |
||
489 | |||
490 | ksort($posList); |
||
491 | |||
492 | // for each MeekroDB variable, substitute it with array(type: i, value: 53) or whatever |
||
493 | $chunkyQuery = array(); // preparsed query |
||
494 | $pos_adj = 0; // how much we've added or removed from the original sql string |
||
495 | foreach ($posList as $pos => $type) { |
||
496 | $type = substr($type, $param_char_length); // variable, without % in front of it |
||
497 | $length_type = strlen($type) + $param_char_length; // length of variable w/o % |
||
498 | |||
499 | $new_pos = $pos + $pos_adj; // position of start of variable |
||
500 | $new_pos_back = $new_pos + $length_type; // position of end of variable |
||
501 | $arg_number_length = 0; // length of any named or numbered parameter addition |
||
502 | |||
503 | // handle numbered parameters |
||
504 | if ($arg_number_length = strspn($sql, '0123456789', $new_pos_back)) { |
||
505 | $arg_number = substr($sql, $new_pos_back, $arg_number_length); |
||
506 | if (!array_key_exists($arg_number, $args_all)) { |
||
507 | $this->nonSQLError("Non existent argument reference (arg $arg_number): $sql"); |
||
508 | } |
||
509 | |||
510 | $arg = $args_all[$arg_number]; |
||
511 | |||
512 | // handle named parameters |
||
513 | } else if (substr($sql, $new_pos_back, $named_seperator_length) == $this->named_param_separator) { |
||
514 | $arg_number_length = strspn($sql, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_', |
||
515 | $new_pos_back + $named_seperator_length) + $named_seperator_length; |
||
516 | |||
517 | $arg_number = substr($sql, $new_pos_back + $named_seperator_length, $arg_number_length - $named_seperator_length); |
||
518 | if (count($args_all) != 1 || !is_array($args_all[0])) { |
||
519 | $this->nonSQLError("If you use named parameters, the second argument must be an array of parameters"); |
||
520 | } |
||
521 | if (!array_key_exists($arg_number, $args_all[0])) { |
||
522 | $this->nonSQLError("Non existent argument reference (arg $arg_number): $sql"); |
||
523 | } |
||
524 | |||
525 | $arg = $args_all[0][$arg_number]; |
||
526 | |||
527 | } else { |
||
528 | $arg_number = 0; |
||
529 | $arg = array_shift($args); |
||
530 | } |
||
531 | |||
532 | if ($new_pos > 0) { |
||
533 | $chunkyQuery[] = substr($sql, 0, $new_pos); |
||
534 | } |
||
535 | |||
536 | if (is_object($arg) && ($arg instanceof WhereClause)) { |
||
537 | list($clause_sql, $clause_args) = $arg->textAndArgs(); |
||
538 | array_unshift($clause_args, $clause_sql); |
||
539 | $preparsed_sql = call_user_func_array(array($this, 'preparseQueryParams'), $clause_args); |
||
540 | $chunkyQuery = array_merge($chunkyQuery, $preparsed_sql); |
||
541 | } else { |
||
542 | $chunkyQuery[] = array('type' => $type, 'value' => $arg); |
||
543 | } |
||
544 | |||
545 | $sql = substr($sql, $new_pos_back + $arg_number_length); |
||
546 | $pos_adj -= $new_pos_back + $arg_number_length; |
||
547 | } |
||
548 | |||
549 | if (strlen($sql) > 0) { |
||
550 | $chunkyQuery[] = $sql; |
||
551 | } |
||
552 | |||
553 | return $chunkyQuery; |
||
554 | } |
||
555 | |||
556 | protected function escape($str) { return "'".$this->get()->real_escape_string(strval($str))."'"; } |
||
557 | |||
558 | protected function sanitize($value) { |
||
559 | if (is_object($value)) { |
||
560 | if ($value instanceof MeekroDBEval) { |
||
561 | return $value->text; |
||
562 | } else if ($value instanceof DateTime) { |
||
563 | return $this->escape($value->format('Y-m-d H:i:s')); |
||
564 | } else { |
||
565 | return ''; |
||
566 | } |
||
567 | } |
||
568 | |||
569 | if (is_null($value)) { |
||
570 | return $this->usenull ? 'NULL' : "''"; |
||
571 | } else if (is_bool($value)) { |
||
572 | return ($value ? 1 : 0); |
||
573 | } else if (is_int($value)) { |
||
574 | return $value; |
||
575 | } else if (is_float($value)) { |
||
576 | return $value; |
||
577 | } else if (is_array($value)) { |
||
578 | // non-assoc array? |
||
579 | if (array_values($value) === $value) { |
||
580 | if (is_array($value[0])) { |
||
581 | return implode(', ', array_map(array($this, 'sanitize'), $value)); |
||
582 | } else { |
||
583 | return '('.implode(', ', array_map(array($this, 'sanitize'), $value)).')'; |
||
584 | } |
||
585 | } |
||
586 | |||
587 | $pairs = array(); |
||
588 | foreach ($value as $k => $v) { |
||
589 | $pairs[] = $this->formatTableName($k).'='.$this->sanitize($v); |
||
590 | } |
||
591 | |||
592 | return implode(', ', $pairs); |
||
593 | } else { |
||
594 | return $this->escape($value); |
||
595 | } |
||
596 | } |
||
597 | |||
598 | protected function parseTS($datets) { |
||
599 | if (is_string($datets)) { |
||
600 | return date('Y-m-d H:i:s', strtotime($datets)); |
||
601 | } else if (is_object($datets) && ($datets instanceof DateTime)) { |
||
602 | return $datets->format('Y-m-d H:i:s'); |
||
603 | } |
||
604 | } |
||
605 | |||
606 | protected function intval($var) { |
||
607 | if (PHP_INT_SIZE == 8) { |
||
608 | return intval($var); |
||
609 | } |
||
610 | return floor(doubleval($var)); |
||
611 | } |
||
612 | |||
613 | protected function parseQueryParams() { |
||
614 | $args = func_get_args(); |
||
615 | $chunkyQuery = call_user_func_array(array($this, 'preparseQueryParams'), $args); |
||
616 | |||
617 | $query = ''; |
||
618 | $array_types = array('ls', 'li', 'ld', 'lb', 'll', 'lt'); |
||
619 | |||
620 | foreach ($chunkyQuery as $chunk) { |
||
621 | if (is_string($chunk)) { |
||
622 | $query .= $chunk; |
||
623 | continue; |
||
624 | } |
||
625 | |||
626 | $type = $chunk['type']; |
||
627 | $arg = $chunk['value']; |
||
628 | $result = ''; |
||
629 | |||
630 | if ($type != '?') { |
||
631 | $is_array_type = in_array($type, $array_types, true); |
||
632 | if ($is_array_type && !is_array($arg)) { |
||
633 | $this->nonSQLError("Badly formatted SQL query: Expected array, got scalar instead!"); |
||
634 | } else if (!$is_array_type && is_array($arg)) { |
||
635 | $this->nonSQLError("Badly formatted SQL query: Expected scalar, got array instead!"); |
||
636 | } |
||
637 | } |
||
638 | |||
639 | if ($type == 's') { |
||
640 | $result = $this->escape($arg); |
||
641 | } else if ($type == 'i') { |
||
642 | $result = $this->intval($arg); |
||
643 | } else if ($type == 'd') { |
||
644 | $result = doubleval($arg); |
||
645 | } else if ($type == 'b') { |
||
646 | $result = $this->formatTableName($arg); |
||
647 | } else if ($type == 'l') { |
||
648 | $result = $arg; |
||
649 | } else if ($type == 'ss') { |
||
650 | $result = $this->escape("%".str_replace(array('%', '_'), array('\%', '\_'), $arg)."%"); |
||
651 | } else if ($type == 't') { |
||
652 | $result = $this->escape($this->parseTS($arg)); |
||
653 | } else if ($type == 'ls') { |
||
654 | $result = array_map(array($this, 'escape'), $arg); |
||
655 | } else if ($type == 'li') { |
||
656 | $result = array_map(array($this, 'intval'), $arg); |
||
657 | } else if ($type == 'ld') { |
||
658 | $result = array_map('doubleval', $arg); |
||
659 | } else if ($type == 'lb') { |
||
660 | $result = array_map(array($this, 'formatTableName'), $arg); |
||
661 | } else if ($type == 'll') { |
||
662 | $result = $arg; |
||
663 | } else if ($type == 'lt') { |
||
664 | $result = array_map(array($this, 'escape'), array_map(array($this, 'parseTS'), $arg)); |
||
665 | } else if ($type == '?') { |
||
666 | $result = $this->sanitize($arg); |
||
667 | } else { |
||
668 | $this->nonSQLError("Badly formatted SQL query: Invalid MeekroDB param $type"); |
||
669 | } |
||
670 | |||
671 | if (is_array($result)) { |
||
672 | $result = '('.implode(',', $result).')'; |
||
673 | } |
||
674 | |||
675 | $query .= $result; |
||
676 | } |
||
677 | |||
678 | return $query; |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * @param string $prepend |
||
683 | */ |
||
684 | protected function prependCall($function, $args, $prepend) { array_unshift($args, $prepend); return call_user_func_array($function, $args); } |
||
685 | public function query() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'assoc'); } |
||
686 | public function queryAllLists() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'list'); } |
||
687 | public function queryFullColumns() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'full'); } |
||
688 | |||
689 | public function queryRaw() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'raw_buf'); } |
||
690 | public function queryRawUnbuf() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'raw_unbuf'); } |
||
691 | |||
692 | protected function queryHelper() { |
||
693 | $args = func_get_args(); |
||
694 | $type = array_shift($args); |
||
695 | $database = $this->get(); |
||
696 | |||
697 | $is_buffered = true; |
||
698 | $row_type = 'assoc'; // assoc, list, raw |
||
699 | $full_names = false; |
||
700 | |||
701 | switch ($type) { |
||
702 | case 'assoc': |
||
703 | break; |
||
704 | case 'list': |
||
705 | $row_type = 'list'; |
||
706 | break; |
||
707 | case 'full': |
||
708 | $row_type = 'list'; |
||
709 | $full_names = true; |
||
710 | break; |
||
711 | case 'raw_buf': |
||
712 | $row_type = 'raw'; |
||
713 | break; |
||
714 | case 'raw_unbuf': |
||
715 | $is_buffered = false; |
||
716 | $row_type = 'raw'; |
||
717 | break; |
||
718 | default: |
||
719 | $this->nonSQLError('Error -- invalid argument to queryHelper!'); |
||
720 | } |
||
721 | |||
722 | $sql = call_user_func_array(array($this, 'parseQueryParams'), $args); |
||
723 | |||
724 | if ($this->success_handler) { |
||
725 | $starttime = microtime(true); |
||
726 | } |
||
727 | $result = $database->query($sql, $is_buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT); |
||
728 | if ($this->success_handler) { |
||
729 | $runtime = microtime(true) - $starttime; |
||
730 | } else { |
||
731 | $runtime = 0; |
||
732 | } |
||
733 | |||
734 | // ----- BEGIN ERROR HANDLING |
||
735 | if (!$sql || $database->error) { |
||
736 | if ($this->error_handler) { |
||
737 | $db_error = $database->error; |
||
738 | $db_errno = $database->errno; |
||
739 | |||
740 | if (isset($_SESSION['user_id'])) { |
||
741 | $database->query( |
||
742 | "INSERT INTO ".$GLOBALS['pre']."log_system SET |
||
743 | date=".time().", |
||
744 | qui=".$_SESSION['user_id'].", |
||
745 | label='Query: ".addslashes($sql)."<br />Error: ".addslashes($db_error)."<br />@ ".addslashes(filter_var($_SERVER['REQUEST_URI'], FILTER_SANITIZE_STRING))."', |
||
746 | type='error'", |
||
747 | MYSQLI_USE_RESULT |
||
748 | ); |
||
749 | } |
||
750 | |||
751 | $error_handler = is_callable($this->error_handler) ? $this->error_handler : 'meekrodb_error_handler'; |
||
752 | |||
753 | call_user_func($error_handler, array( |
||
754 | 'type' => 'sql', |
||
755 | 'query' => $sql, |
||
756 | 'error' => $db_error, |
||
757 | 'code' => $db_errno |
||
758 | )); |
||
759 | } |
||
760 | |||
761 | if ($this->throw_exception_on_error) { |
||
762 | $exeption = new MeekroDBException($db_error, $sql, $db_errno); |
||
763 | throw $exeption; |
||
764 | } |
||
765 | } else if ($this->success_handler) { |
||
766 | $runtime = sprintf('%f', $runtime * 1000); |
||
767 | $success_handler = is_callable($this->success_handler) ? $this->success_handler : 'meekrodb_debugmode_handler'; |
||
768 | |||
769 | call_user_func($success_handler, array( |
||
770 | 'query' => $sql, |
||
771 | 'runtime' => $runtime, |
||
772 | 'affected' => $database->affected_rows |
||
773 | )); |
||
774 | } |
||
775 | |||
776 | // ----- END ERROR HANDLING |
||
777 | |||
778 | $this->insert_id = $database->insert_id; |
||
779 | $this->affected_rows = $database->affected_rows; |
||
780 | |||
781 | // mysqli_result->num_rows won't initially show correct results for unbuffered data |
||
782 | if ($is_buffered && ($result instanceof MySQLi_Result)) { |
||
783 | $this->num_rows = $result->num_rows; |
||
784 | } else { |
||
785 | $this->num_rows = null; |
||
786 | } |
||
787 | |||
788 | if ($row_type == 'raw' || !($result instanceof MySQLi_Result)) { |
||
789 | return $result; |
||
790 | } |
||
791 | |||
792 | $return = array(); |
||
793 | |||
794 | if ($full_names) { |
||
795 | $infos = array(); |
||
796 | foreach ($result->fetch_fields() as $info) { |
||
797 | if (strlen($info->table)) { |
||
798 | $infos[] = $info->table.'.'.$info->name; |
||
799 | } else { |
||
800 | $infos[] = $info->name; |
||
801 | } |
||
802 | } |
||
803 | } |
||
804 | |||
805 | while ($row = ($row_type == 'assoc' ? $result->fetch_assoc() : $result->fetch_row())) { |
||
806 | if ($full_names) { |
||
807 | $row = array_combine($infos, $row); |
||
808 | } |
||
809 | $return[] = $row; |
||
810 | } |
||
811 | |||
812 | // free results |
||
813 | $result->free(); |
||
814 | while ($database->more_results()) { |
||
815 | $database->next_result(); |
||
816 | if ($result = $database->use_result()) { |
||
817 | $result->free(); |
||
818 | } |
||
819 | } |
||
820 | |||
821 | return $return; |
||
822 | } |
||
823 | |||
824 | public function queryOneRow() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstRow'), $args); } |
||
825 | public function queryFirstRow() { |
||
826 | $args = func_get_args(); |
||
827 | $result = call_user_func_array(array($this, 'query'), $args); |
||
828 | if (!$result) { |
||
829 | return null; |
||
830 | } |
||
831 | return reset($result); |
||
832 | } |
||
833 | |||
834 | public function queryOneList() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstList'), $args); } |
||
835 | public function queryFirstList() { |
||
836 | $args = func_get_args(); |
||
837 | $result = call_user_func_array(array($this, 'queryAllLists'), $args); |
||
838 | if (!$result) { |
||
839 | return null; |
||
840 | } |
||
841 | return reset($result); |
||
842 | } |
||
843 | |||
844 | public function queryFirstColumn() { |
||
845 | $args = func_get_args(); |
||
846 | $results = call_user_func_array(array($this, 'queryAllLists'), $args); |
||
847 | $ret = array(); |
||
848 | |||
849 | if (!count($results) || !count($results[0])) { |
||
850 | return $ret; |
||
851 | } |
||
852 | |||
853 | foreach ($results as $row) { |
||
854 | $ret[] = $row[0]; |
||
855 | } |
||
856 | |||
857 | return $ret; |
||
858 | } |
||
859 | |||
860 | public function queryOneColumn() { |
||
861 | $args = func_get_args(); |
||
862 | $column = array_shift($args); |
||
863 | $results = call_user_func_array(array($this, 'query'), $args); |
||
864 | $ret = array(); |
||
865 | |||
866 | if (!count($results) || !count($results[0])) { |
||
867 | return $ret; |
||
868 | } |
||
869 | if ($column === null) { |
||
870 | $keys = array_keys($results[0]); |
||
871 | $column = $keys[0]; |
||
872 | } |
||
873 | |||
874 | foreach ($results as $row) { |
||
875 | $ret[] = $row[$column]; |
||
876 | } |
||
877 | |||
878 | return $ret; |
||
879 | } |
||
880 | |||
881 | public function queryFirstField() { |
||
882 | $args = func_get_args(); |
||
883 | $row = call_user_func_array(array($this, 'queryFirstList'), $args); |
||
884 | if ($row == null) { |
||
885 | return null; |
||
886 | } |
||
887 | return $row[0]; |
||
888 | } |
||
889 | |||
890 | public function queryOneField() { |
||
891 | $args = func_get_args(); |
||
892 | $column = array_shift($args); |
||
893 | |||
894 | $row = call_user_func_array(array($this, 'queryOneRow'), $args); |
||
895 | if ($row == null) { |
||
896 | return null; |
||
897 | } else if ($column === null) { |
||
898 | $keys = array_keys($row); |
||
899 | $column = $keys[0]; |
||
900 | } |
||
901 | |||
902 | return $row[$column]; |
||
903 | } |
||
904 | } |
||
905 | |||
1102 | } |