Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Storage - Administração da base de dados com MySQLi
- * Última modificação: 2026-03-13
- */
- class Storage {
- /**
- * Configurações de conexão
- */
- public
- $hostname = '',
- $username = '',
- $password = '',
- $database = '',
- $encoding = 'utf8mb4';
- /**
- * Histórico, cache e estado interno
- */
- private
- $connect,
- $query,
- $last,
- $history = [],
- $cache = [],
- $in_transaction = false,
- $profile = [];
- // -------------------------------------------------------------------------
- // CONEXÃO
- // -------------------------------------------------------------------------
- /**
- * Conexão inteligente: conecta, seleciona banco e define charset automaticamente
- */
- public function __construct() {
- if (!empty($this->hostname) && !empty($this->username)) {
- $this->connect();
- if (!empty($this->database)) {
- $this->select_db();
- }
- if (!empty($this->encoding)) {
- $this->set_character();
- }
- }
- }
- /**
- * Inicia a conexão com o servidor
- *
- * @throws RuntimeException
- */
- public function connect(): void {
- $this->connect = mysqli_connect(
- $this->hostname,
- $this->username,
- $this->password
- );
- if (!$this->connect) {
- throw new RuntimeException(
- 'Erro ao conectar ao servidor: ' . mysqli_connect_error()
- );
- }
- }
- /**
- * Encerra a conexão com o servidor
- */
- public function disconnect(): bool {
- return mysqli_close($this->connect);
- }
- /**
- * Seleciona uma base de dados
- *
- * @throws RuntimeException
- */
- public function select_db(): void {
- if (!mysqli_select_db($this->connect, $this->database)) {
- throw new RuntimeException(
- 'Erro ao selecionar a base de dados: ' . $this->connect->error
- );
- }
- }
- /**
- * Define o conjunto de caracteres da conexão
- *
- * @throws RuntimeException
- */
- public function set_character(): void {
- $enc = $this->encoding;
- $sql = "SET character_set_results = '{$enc}', NAMES '{$enc}',
- character_set_connection = '{$enc}',
- character_set_database = '{$enc}',
- character_set_server = '{$enc}';";
- if (!mysqli_query($this->connect, $sql)) {
- throw new RuntimeException(
- 'Erro ao definir charset: ' . mysqli_error($this->connect)
- );
- }
- }
- /**
- * Verifica conexão ativa e executa uma query
- * Registra no histórico com timestamp e duração
- *
- * @param string $value SQL a executar, ou 'LAST' para repetir a última
- * @return $this
- * @throws RuntimeException
- */
- public function query(string $value): static {
- if (!isset($this->connect) || !mysqli_ping($this->connect)) {
- $this->__construct();
- }
- $sql = ($value === 'LAST') ? $this->last : $value;
- $start = microtime(true);
- $result = mysqli_query($this->connect, $sql);
- $duration = round((microtime(true) - $start) * 1000, 3);
- if ($result === false) {
- throw new RuntimeException(
- 'Erro ao efetuar a consulta: ' . mysqli_error($this->connect)
- . ' | SQL: ' . $sql
- );
- }
- $this->query = $result;
- $this->last = $sql;
- $this->history[] = [
- 'time' => date('Y-m-d H:i:s'),
- 'query' => $sql,
- 'duration' => $duration . 'ms',
- ];
- $this->profile[] = $duration;
- return $this;
- }
- /**
- * Executa uma query com prepared statement (proteção contra SQL injection)
- *
- * @param string $query SQL com placeholders '?'
- * @param array $params Valores a substituir nos placeholders
- * @param string $types Tipos dos parâmetros: s=string, i=int, d=double, b=blob
- * (detectado automaticamente se omitido)
- * @return $this
- * @throws RuntimeException
- *
- * @example
- * $db->prepare("SELECT * FROM users WHERE email = ? AND active = ?", ['[email protected]', 1], 'si');
- */
- public function prepare(string $query, array $params = [], string $types = ''): static {
- if (!isset($this->connect) || !mysqli_ping($this->connect)) {
- $this->__construct();
- }
- $stmt = mysqli_prepare($this->connect, $query);
- if (!$stmt) {
- throw new RuntimeException(
- 'Erro ao preparar statement: ' . mysqli_error($this->connect)
- );
- }
- if (!empty($params)) {
- if (empty($types)) {
- $types = implode('', array_map(function ($v) {
- return is_int($v) ? 'i' : (is_float($v) ? 'd' : 's');
- }, $params));
- }
- mysqli_stmt_bind_param($stmt, $types, ...$params);
- }
- $start = microtime(true);
- mysqli_stmt_execute($stmt);
- $duration = round((microtime(true) - $start) * 1000, 3);
- $this->query = mysqli_stmt_get_result($stmt);
- $this->last = $query;
- $this->history[] = [
- 'time' => date('Y-m-d H:i:s'),
- 'query' => $query,
- 'params' => $params,
- 'duration' => $duration . 'ms',
- ];
- $this->profile[] = $duration;
- return $this;
- }
- /**
- * Executa uma query com cache em memória
- * Reutiliza o resultado se a mesma query já foi executada nesta requisição
- *
- * @param string $query SQL a executar
- * @param int|null $ttl Tempo de vida em segundos (null = sem expiração)
- * @return $this
- *
- * @example
- * $db->query_cache("SELECT * FROM config", 60)->fetch_assoc();
- */
- public function query_cache(string $query, ?int $ttl = null): static {
- $key = md5($query);
- $now = time();
- if (isset($this->cache[$key])) {
- $entry = $this->cache[$key];
- if ($ttl === null || ($now - $entry['time']) < $ttl) {
- $this->query = $entry['result'];
- return $this;
- }
- }
- $this->query($query);
- // Armazena uma cópia dos dados (não o resource, que é single-read)
- $rows = [];
- if ($this->query && is_object($this->query)) {
- while ($row = $this->query->fetch_array(MYSQLI_ASSOC)) {
- $rows[] = $row;
- }
- $this->cache[$key] = ['result' => $rows, 'time' => $now];
- // Cria um "resultado virtual" iterável via array
- $this->query = $rows;
- }
- return $this;
- }
- /**
- * Limpa todo o cache de queries ou uma entrada específica
- *
- * @param string|null $query SQL específico para limpar (null = limpa tudo)
- */
- public function clear_cache(?string $query = null): void {
- if ($query === null) {
- $this->cache = [];
- } else {
- unset($this->cache[md5($query)]);
- }
- }
- // -------------------------------------------------------------------------
- // INSERT / UPDATE / DELETE
- // -------------------------------------------------------------------------
- /**
- * Insere um registro usando array associativo
- * Usa escape em todos os valores
- *
- * @param string $table Nome da tabela
- * @param array $array Dados no formato ['coluna' => 'valor']
- * @return $this
- *
- * @example
- * $db->insert_array('users', ['name' => 'Ana', 'email' => '[email protected]']);
- */
- public function insert_array(string $table, array $array): static {
- foreach ($array as $key => $value) {
- $columns[] = '`' . $key . '`';
- $fields[] = ($value !== '' && $value !== null)
- ? "'" . $this->escape((string) $value) . "'"
- : 'NULL';
- }
- $columns = implode(', ', $columns);
- $fields = implode(', ', $fields);
- return $this->query("INSERT INTO `{$table}` ({$columns}) VALUES ({$fields});");
- }
- /**
- * Atualiza registros usando array associativo
- * Usa escape em todos os valores
- *
- * @param string $table Nome da tabela
- * @param array $array Dados no formato ['coluna' => 'valor']
- * @param string $where Condição WHERE (sem a palavra WHERE)
- * @return $this
- * @throws InvalidArgumentException
- *
- * @example
- * $db->update_array('users', ['name' => 'João'], "id = 5");
- */
- public function update_array(string $table, array $array, string $where): static {
- if (empty($where)) {
- throw new InvalidArgumentException(
- 'update_array() requer uma condição WHERE para evitar atualização de todos os registros.'
- );
- }
- foreach ($array as $key => $value) {
- $set[] = '`' . $key . '` = ' . (
- ($value !== '' && $value !== null)
- ? "'" . $this->escape((string) $value) . "'"
- : 'NULL'
- );
- }
- $set = implode(', ', $set);
- return $this->query("UPDATE `{$table}` SET {$set} WHERE {$where};");
- }
- /**
- * Exclui registros de uma tabela com condição obrigatória
- *
- * @param string $table Nome da tabela
- * @param string $where Condição WHERE (sem a palavra WHERE)
- * @return $this
- * @throws InvalidArgumentException
- *
- * @example
- * $db->delete_where('users', "id = 10");
- */
- public function delete_where(string $table, string $where): static {
- if (empty($where)) {
- throw new InvalidArgumentException(
- 'delete_where() requer uma condição WHERE para evitar exclusão de todos os registros.'
- );
- }
- return $this->query("DELETE FROM `{$table}` WHERE {$where};");
- }
- // -------------------------------------------------------------------------
- // FETCH
- // -------------------------------------------------------------------------
- /**
- * Retorna todos os resultados como array numérico e associativo
- */
- public function fetch_array(): array {
- // Suporte a resultado em cache (array nativo)
- if (is_array($this->query)) {
- return $this->query;
- }
- while ($array[] = $this->query->fetch_array(MYSQLI_BOTH));
- return array_filter($array);
- }
- /**
- * Retorna todos os resultados como array associativo
- */
- public function fetch_assoc(): array {
- if (is_array($this->query)) {
- return $this->query;
- }
- while ($array[] = $this->query->fetch_array(MYSQLI_ASSOC));
- return array_filter($array);
- }
- /**
- * Retorna todos os resultados como array numérico
- */
- public function fetch_row(): array {
- if (is_array($this->query)) {
- return array_values($this->query);
- }
- while ($array[] = $this->query->fetch_array(MYSQLI_NUM));
- return array_filter($array);
- }
- /**
- * Retorna apenas o primeiro resultado como array associativo (ou null)
- *
- * @example
- * $user = $db->query("SELECT * FROM users WHERE id = 1")->fetch_one();
- */
- public function fetch_one(): ?array {
- if (is_array($this->query)) {
- return $this->query[0] ?? null;
- }
- return $this->query->fetch_array(MYSQLI_ASSOC) ?: null;
- }
- /**
- * Retorna o valor de uma única coluna do primeiro resultado
- *
- * @param string|int $column Nome ou índice da coluna (padrão: primeira coluna)
- * @return mixed
- *
- * @example
- * $total = $db->query("SELECT COUNT(*) FROM users")->fetch_value();
- * $email = $db->query("SELECT email FROM users WHERE id = 1")->fetch_value('email');
- */
- public function fetch_value(string|int $column = 0): mixed {
- if (is_array($this->query)) {
- $row = $this->query[0] ?? null;
- return $row ? (is_int($column) ? array_values($row)[$column] : $row[$column]) : null;
- }
- $row = $this->query->fetch_array(MYSQLI_BOTH);
- return $row[$column] ?? null;
- }
- /**
- * Retorna os nomes das colunas do resultado
- */
- public function fetch_field(): array {
- while ($field = $this->query->fetch_field()) {
- $array[] = $field->name;
- }
- return $array ?? [];
- }
- // -------------------------------------------------------------------------
- // PAGINAÇÃO
- // -------------------------------------------------------------------------
- /**
- * Executa uma query com paginação automática (LIMIT + OFFSET)
- *
- * @param string $query SQL sem LIMIT/OFFSET
- * @param int $page Número da página (começa em 1)
- * @param int $per_page Registros por página
- * @return $this
- *
- * @example
- * $db->paginate("SELECT * FROM products ORDER BY name", 2, 10)->fetch_assoc();
- */
- public function paginate(string $query, int $page = 1, int $per_page = 10): static {
- $page = max(1, $page);
- $offset = ($page - 1) * $per_page;
- return $this->query($query . " LIMIT {$per_page} OFFSET {$offset}");
- }
- /**
- * Conta o total de registros de uma tabela com condição opcional
- *
- * @param string $table Nome da tabela
- * @param string $where Condição WHERE opcional (padrão: todos os registros)
- * @return int
- *
- * @example
- * $total = $db->count('users');
- * $ativos = $db->count('users', "active = 1");
- */
- public function count(string $table, string $where = '1'): int {
- return (int) $this->query(
- "SELECT COUNT(*) FROM `{$table}` WHERE {$where}"
- )->fetch_value();
- }
- // -------------------------------------------------------------------------
- // TRANSAÇÕES
- // -------------------------------------------------------------------------
- /**
- * Inicia uma transação
- *
- * @return $this
- *
- * @example
- * $db->begin_transaction();
- * try {
- * $db->insert_array('accounts', [...]);
- * $db->update_array('balances', [...], "id = 1");
- * $db->commit();
- * } catch (Exception $e) {
- * $db->rollback();
- * }
- */
- public function begin_transaction(): static {
- mysqli_autocommit($this->connect, false);
- mysqli_begin_transaction($this->connect);
- $this->in_transaction = true;
- return $this;
- }
- /**
- * Confirma a transação atual
- *
- * @return $this
- * @throws RuntimeException
- */
- public function commit(): static {
- if (!$this->in_transaction) {
- throw new RuntimeException('Nenhuma transação ativa para confirmar.');
- }
- mysqli_commit($this->connect);
- mysqli_autocommit($this->connect, true);
- $this->in_transaction = false;
- return $this;
- }
- /**
- * Desfaz a transação atual
- *
- * @return $this
- */
- public function rollback(): static {
- if ($this->in_transaction) {
- mysqli_rollback($this->connect);
- mysqli_autocommit($this->connect, true);
- $this->in_transaction = false;
- }
- return $this;
- }
- /**
- * Verifica se há uma transação ativa
- */
- public function is_in_transaction(): bool {
- return $this->in_transaction;
- }
- // -------------------------------------------------------------------------
- // SEGURANÇA E UTILITÁRIOS
- // -------------------------------------------------------------------------
- /**
- * Escapa uma string para uso seguro em queries
- *
- * @param string $value Valor a escapar
- * @return string
- *
- * @example
- * $nome = $db->escape($_POST['nome']);
- * $db->query("SELECT * FROM users WHERE name = '{$nome}'");
- */
- public function escape(string $value): string {
- return mysqli_real_escape_string($this->connect, $value);
- }
- /**
- * Executa EXPLAIN na última query e retorna o plano de execução
- * Útil para identificar queries lentas e falta de índices
- *
- * @return array
- *
- * @example
- * $db->query("SELECT * FROM orders WHERE user_id = 1");
- * $plan = $db->explain();
- */
- public function explain(): array {
- if (empty($this->last)) {
- return [];
- }
- return $this->query("EXPLAIN {$this->last}")->fetch_assoc();
- }
- // -------------------------------------------------------------------------
- // INFORMAÇÕES DO RESULTADO
- // -------------------------------------------------------------------------
- /** Número de linhas retornadas */
- public function num_rows(): int {
- if (is_array($this->query)) {
- return count($this->query);
- }
- return mysqli_num_rows($this->query);
- }
- /** Número de colunas no resultado */
- public function num_fields(): int {
- return mysqli_field_count($this->connect);
- }
- /** Número de linhas afetadas pela última operação */
- public function affected_rows(): int {
- return mysqli_affected_rows($this->connect);
- }
- /** ID gerado pelo último INSERT */
- public function insert_id(): int {
- return mysqli_insert_id($this->connect);
- }
- // -------------------------------------------------------------------------
- // HISTÓRICO E PERFORMANCE
- // -------------------------------------------------------------------------
- /**
- * Retorna o histórico completo de queries executadas
- */
- public function get_history(): array {
- return $this->history;
- }
- /**
- * Retorna métricas de performance das queries
- * Inclui total de queries, tempo total e tempo médio
- *
- * @return array
- *
- * @example
- * $metrics = $db->get_profile();
- * echo "Queries: {$metrics['total']} | Tempo total: {$metrics['total_ms']}ms";
- */
- public function get_profile(): array {
- $total = count($this->profile);
- $sum = array_sum($this->profile);
- return [
- 'total' => $total,
- 'total_ms' => round($sum, 3),
- 'avg_ms' => $total > 0 ? round($sum / $total, 3) : 0,
- 'slowest' => $total > 0 ? max($this->profile) : 0,
- 'queries' => $this->profile,
- ];
- }
- // -------------------------------------------------------------------------
- // MEMÓRIA
- // -------------------------------------------------------------------------
- /**
- * Libera o resultado atual da memória
- */
- public function free_result(): bool {
- if (is_object($this->query)) {
- return mysqli_free_result($this->query);
- }
- return false;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment