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 | } |