11:58
Класс для БД — Лисья нора
§ Программный код
Редакция: 5 мар 2024use mysqli; class DatabaseConnector { /** @var mysqli $db */ protected $db; protected $db_host; protected $db_user; protected $db_pass; protected $db_name; protected $db_port; protected $db_codepage; // ================ БАЗОВЫЕ ============================ /** * db constructor. * @param mixed $cdb * @param string $codepage */ public function __construct($cdb, string $codepage = 'utf8') { $this->db_port = null; // Коннект по массиву if (is_array($cdb)) { $this->db_host = $cdb['host'] ?? ''; $this->db_user = $cdb['username'] ?? ''; $this->db_pass = $cdb['password'] ?? ''; $this->db_name = $cdb['database'] ?? ''; $codepage = $cdb['charset'] ?? $codepage; } // Коннект по строке else if (preg_match('~-h\s+([^\s+]+)\s+-u\s+([^\s+]+)\s+-p([^\s+]*)\s+([^\s+]+)~', $cdb, $c)) { list(, $this->db_host, $this->db_user, $this->db_pass, $this->db_name) = $c; } // SphinxConnect: 127.0.0.1:9 else if (preg_match('~^([0-9.]+):(\d+)$~', $cdb, $c)) { $this->db_host = $c[1]; $this->db_user = ''; $this->db_pass = ''; $this->db_name = ''; $this->db_port = $c[2]; } else { die('['.date('Y-m-d H:i:s')." Invalid MySQL connection string\n"); } $this->db_codepage = $codepage; } // Подключиться к БД public function connect() { if (empty($this->db)) { $err_cstr = "$this->db_host:$this->db_user:$this->db_name"; try { $this->db = new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name, $this->db_port); } catch (\Exception $e) { die("CONNECT ERROR: " . $e->getMessage() . " [$err_cstr]"); } if ($this->db->connect_error) { die('['.date('Y-m-d H:i:s')." Can't connect to Db... [$err_cstr]\n"); } $this->db->query("SET NAMES $this->db_codepage"); } } // Отключиться от БД public function close() { if ($this->db) { $this->db->close(); $this->db = null; } } // Переключить БД public function use($dbname) { $this->connect(); $this->query("USE $dbname"); return $this; } // Выполнить запрос $sql public function query($sql, $param = "") { $this->connect(); $q = $this->db->query($sql); if ($this->db->errno) { throw new \Exception("Error in mysql: " . $this->db->error . " [$sql]"); } $param = explode(':', $param); // Выдать все запрошенные строки if ($param[0] == 'rows') { $rows = []; $keyname = $param[1] ?? ""; foreach ($q as $item) $rows[] = $keyname ? $item[$keyname] : $item; return $rows; } return $q; } /** @return mixed */ public function affected_rows() { return $this->db->affected_rows; } /** @return mysqli */ public function db() { return $this->db; } // Обезопасить строку public function escape($s) { $this->connect(); if (is_null($s)) return 'NULL'; else if (is_integer($s) || is_float($s) || is_double($s)) return $s; else if (is_object($s) || is_array($s)) return "'".$this->db->real_escape_string(json_encode($s, JSON_UNESCAPED_SLASHES))."'"; else return "'".$this->db->real_escape_string($s)."'"; } // Обезопасить массив входящих данных public function escape_arr(array $data) { foreach ($data as $id => $value) $data[$id] = $this->escape($value); return $data; } // Запросы WHERE // [f1=> ..., f2 => ...] Одиночный запрос // [[f1 => ..., f2 => ...], .... ] Множественный запрос protected function where($where) { $arr = []; foreach ($where as $k => $v) if (is_array($v)) foreach ($v as $a => $b) $arr[$k][] = "$a = ".$this->escape($b); else $arr[0][] = "$k = ".$this->escape($v); return join(" OR ", array_map(function ($a) { return '(' . join(" AND ", $a) . ')'; }, $arr)); } // =============================== ОДИНОЧНЫЕ ЗАПРОСЫ =============================== // Выбор из таблицы по заданным полям public function select($table, $where) { $param = explode(':', $table); $table = $param[0]; $field = $param[1] ?? '*'; return $this->query("SELECT $field FROM $table WHERE " . $this->where($where)); } // Вставить новую строку $data в $table public function insert($table, array $data, $ignore = 'IGNORE') { $data = $this->escape_arr($data); $sql = "INSERT $ignore INTO $table (`".join("`, `", array_keys($data))."`) "; $sql .= "VALUES (".join(", ", array_values($data)).")"; $this->query($sql); return (int) $this->db->insert_id; } // Обновить данные $data в $where public function update($table, array $data, array $where) { $sets = []; foreach ($this->escape_arr($data) as $id => $value) $sets[] = "`$id` = $value"; $this->query("UPDATE $table SET ".join(', ', $sets)." WHERE " . $this->where($where)); } // =============================== ГРУППОВЫЕ ЗАПРОСЫ =============================== // Групповой запрос к таблице по ключу, $table:[$key=id]:[$fields=*]] // Если *key, то получается ассоциативный массив по ключу public function wherein($tablekey, $arr, $where = '') { $rows = []; $r = explode(':', $tablekey); $table = $r[0]; $key = ($r[1] ?? '') ?: 'id'; $field = ($r[2] ?? '') ?: '*'; if ($arr = $this->escape_arr(array_unique($arr))) { $ext = $key[0]; $key = ltrim($key, '*'); $sql = "SELECT $field FROM $table WHERE $key IN (".join(',', $arr).")" . ($where ? " AND $where" : ""); foreach ($this->query($sql) as $row) { if ($ext == '*') { $kval = $row[$key]; unset($row[$key]); $rows[$kval] = $row; } else { $rows[] = $row; } } } return $rows; } // Пакетное удаление строк, table[:keyname=id] public function deletein($tablekey, $arr) { $r = explode(':', $tablekey); $table = $r[0]; $key = ($r[1] ?? '') ?: 'id'; $this->query("DELETE FROM $table WHERE $key IN (".join(',', $this->escape_arr($arr)).")"); return $this->db->affected_rows; } // Добавление пачки строк за один запрос // У всех строк ДОЛЖЕН БЫТЬ один формат полей, и идти в одном порядке // Если задан $names, то порядок имен идет оттуда public function batch_insert($table, $rows, $names = []) { if (empty($rows)) return 0; $names = $names ?: array_keys(current($rows)); $sql = "INSERT IGNORE INTO $table (`".join("`,`", $names)."`) VALUES "; foreach ($rows as $id => $row) { $rows[$id] = is_array($row) ? '('.join(",", array_values($this->escape_arr($row))).')' : '('.$this->escape($row).')'; } $this->query($sql . join(',', $rows)); return $this->db->affected_rows; } // Обновление пачкой $table = "tablename[:id:[pluck]]": $rows[key] => data public function batch_update($table, $rows, $only_sql = false) { if (empty($rows)) return 0; $param = explode(':', $table); $table = $param[0]; $key = ($param[1] ?? '') ?: 'id'; $pluck = ($param[2] ?? '') ?: ''; $sets = []; $keys = []; $update = []; foreach ($rows as $id => $row) { // Обновление только одного заданного поля if ($pluck) $row = [$pluck => $row]; $keys[] = ($id = $this->escape($id)); foreach ($this->escape_arr($row) as $name => $v) $update[$name][$id] = $v; } foreach ($update as $name => $values) { $sets[$name] = "$name = CASE\n"; foreach ($values as $id => $value) $sets[$name] .= " WHEN $key = $id THEN $value\n"; $sets[$name] .= "ELSE $name END"; } $sql = "UPDATE $table SET " . join(', ', $sets) . " WHERE $key IN (".join(',', $keys).")"; if ($only_sql) { return $sql; } else { $this->query($sql); return (int) $this->db->affected_rows; } } // =============================== РАСШИРЕННЫЕ =============================== // Извлечение данных порциями // Пример использования foreach ($db->rows('tablename:[id=имя ключа]:[group_cnt=кол-во в группе выдачи]') as $row) public function chunk($table, $pt = null) { $request = explode(':', $table); $table = $request[0]; // Рабочая таблица $keyname = ($request[1] ?? '') ?: 'id'; // Ключ $grpcnt = ($request[2] ?? '') ?: 0; // Вместо одной строки, выдать группу из N строк // FIELD задает поля, которые выводятся по умолчанию все; JOIN подцепляет запросы $arr = is_array($pt); $field = $arr && isset($pt[0]) ? $pt[0] : '*'; $where = $arr && isset($pt['w']) ? $pt['w'] : (is_string($pt) ? $pt : ''); $join = $arr && isset($pt['j']) ? $pt['j'] : ''; $steps = $arr && isset($pt['s']) ? $pt['s'] : 5000; $gkeyname = $keyname; if ($grpcnt && preg_match('~\.(\w+)$~', $keyname, $c)) $gkeyname = $c[1]; list($min, $max) = $this->query("SELECT MIN($keyname), MAX($keyname) FROM $table")->fetch_row(); $step = (int)(($max - $min) / $steps); $step = max($step, 100); $rows = []; for ($id = $min; $id < $max; $id += $step) { $sql = "SELECT $field FROM $table $join ". "WHERE ($keyname >= $id AND $keyname < $id + $step)" . ($where ? " AND $where" : ""); foreach ($this->query($sql) as $row) { if ($grpcnt) { $rows[$row[$gkeyname]] = $row; if (count($rows) >= $grpcnt) { yield $rows; $rows = []; } } else { yield $row; } } } if ($grpcnt && $rows) yield $rows; } // Извлечение из arr только необходимого поля public function pluck($rows, $name, $intval = false) { foreach ($rows as $id => $row) { $value = $row[$name] ?? null; $rows[$id] = $intval ? (int) $value : $value; } return $rows; } // Список таблиц в БД // Список таблиц в БД public function tables($filter = '') { $rows = []; foreach ($this->query("SELECT table_name, data_length + index_length AS size FROM information_schema.TABLES WHERE table_schema = '$this->db_name' ORDER BY `table_name` ASC") as $row) { $tb = $row['table_name']; if (!$filter || preg_match($filter, $tb)) $rows[$tb] = $row['size']; } return $rows; } // Минимальное и максимальное значение поля public function minmax($table, $key = 'id') { return $this->query("SELECT MIN($key), MAX($key), COUNT(*) FROM $table")->fetch_row(); } // Подсчет количества в таблице public function count($table, $where = '') { return $this->query("SELECT COUNT(*) FROM $table".($where ? " WHERE $where" : ""))->fetch_row()[0]; } // Сокращеннаая запись public function kk($rows) { return join(',', $this->escape_arr(array_keys($rows))); } public function kv($rows) { return join(',', $this->escape_arr($rows)); } // =============================== HELPERS =============================== // Забронировать следующую порцию очереди в таблице // Формат запроса table:[id=key-name-field]:[status=status-field-name]; $quantity = количество элементов в одном треде public function reserve_queue($table, $thread, $quantity = 250) { $basic = explode(':', $table); $table = $basic[0]; $field = ($basic[1] ?? '') ?: 'id'; $key = ($basic[2] ?? '') ?: 'status'; $sql = "SELECT $field FROM $table WHERE $key = -$thread"; // Одиночный запрос if ($thread < 0) return [-$thread]; if ($rows = $this->query($sql, "rows:$field")) { return $rows; } else { $this->query("UPDATE $table SET $key = -$thread WHERE $key = 0 LIMIT $quantity"); return $this->query($sql, "rows:$field"); } } // Получение статистики по `status` public function status_group($tablename, $status_field = 'status') { $rows = []; foreach ($this->query("SELECT COUNT(*) g, `$status_field` FROM $tablename GROUP BY `$status_field`") as $row) { $rows[$row[$status_field]] = (int) $row['g']; } return $rows; } }