/
home
/
suroeste
/
public_html
/
payments.transportessuroeste.com
/
src
/
Models
/
/home/suroeste/public_html/payments.transportessuroeste.com/src/Models
mkdir
upload
Name
Size
Mode
Actions
Models/
-
0755
rm
Database.php
8086
0644
edit
dl
rm
Edit:
/home/suroeste/public_html/payments.transportessuroeste.com/src/Models/Database.php
(8086B)
<?php /** * ============================================================================ * DATABASE - Conexión Segura con PDO * ============================================================================ */ namespace TransportesSuroeste\Models; use PDO; use PDOException; use TransportesSuroeste\Exceptions\DatabaseException; class Database { private static ?Database $instance = null; private ?PDO $pdo = null; private int $transactionLevel = 0; private function __construct() { $this->connect(); } public static function getInstance(): Database { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } private function connect(): void { try { $dsn = sprintf( 'mysql:host=%s;port=%d;dbname=%s;charset=%s', DB_CONFIG['host'], DB_CONFIG['port'], DB_CONFIG['database'], DB_CONFIG['charset'] ); $this->pdo = new PDO($dsn, DB_CONFIG['username'], DB_CONFIG['password'], DB_CONFIG['options']); $this->pdo->exec("SET SESSION sql_mode = 'STRICT_ALL_TABLES'"); } catch (PDOException $e) { error_log("DB Connection Error: " . $e->getMessage()); throw new DatabaseException('No se pudo conectar a la base de datos', 503, $e, (string)($e->errorInfo[1] ?? '')); } } /** * Obtener conexión PDO directa */ public function getPdo(): PDO { return $this->pdo; } /** * Alias de getPdo para compatibilidad */ public function getConnection(): PDO { return $this->pdo; } /** * Ejecutar consulta preparada (SEGURA contra SQL Injection) */ public function query(string $sql, array $params = []): \PDOStatement { try { $stmt = $this->pdo->prepare($sql); if (empty($params)) { $stmt->execute(); return $stmt; } // Detectar si es array asociativo o indexado $isAssoc = array_keys($params) !== range(0, count($params) - 1); if ($isAssoc) { // Named parameters foreach ($params as $key => $value) { $paramName = ':' . ltrim($key, ':'); $type = $this->getPdoType($value); $stmt->bindValue($paramName, $value, $type); } $stmt->execute(); } else { // Positional parameters $stmt->execute($params); } return $stmt; } catch (PDOException $e) { // Log completo para debug (solo en servidor, nunca al usuario) error_log(sprintf( "SQL Error [%s]: %s | SQL: %s | Params: %s", $e->getCode(), $e->getMessage(), $sql, json_encode($params) )); throw $this->translatePdoException($e); } } /** * Obtener tipo PDO para un valor */ private function getPdoType($value): int { return match (true) { is_int($value) => PDO::PARAM_INT, is_bool($value) => PDO::PARAM_BOOL, is_null($value) => PDO::PARAM_NULL, default => PDO::PARAM_STR }; } /** * Obtener un solo registro */ public function fetchOne(string $sql, array $params = []): ?array { $result = $this->query($sql, $params)->fetch(PDO::FETCH_ASSOC); return $result !== false ? $result : null; } /** * Obtener todos los registros */ public function fetchAll(string $sql, array $params = []): array { return $this->query($sql, $params)->fetchAll(PDO::FETCH_ASSOC); } /** * Insertar registro */ public function insert(string $table, array $data): int { $columns = array_keys($data); $placeholders = array_map(fn($c) => ':' . $c, $columns); $sql = sprintf( 'INSERT INTO `%s` (`%s`) VALUES (%s)', $table, implode('`, `', $columns), implode(', ', $placeholders) ); $this->query($sql, $data); return (int) $this->pdo->lastInsertId(); } /** * Actualizar registros */ public function update(string $table, array $data, string $where, array $whereParams = []): int { $set = []; foreach (array_keys($data) as $col) { $set[] = "`{$col}` = :set_{$col}"; } $sql = sprintf('UPDATE `%s` SET %s WHERE %s', $table, implode(', ', $set), $where); $params = []; foreach ($data as $k => $v) { $params['set_' . $k] = $v; } return $this->query($sql, array_merge($params, $whereParams))->rowCount(); } /** * Eliminar registros */ public function delete(string $table, string $where, array $params = []): int { $sql = sprintf('DELETE FROM `%s` WHERE %s', $table, $where); return $this->query($sql, $params)->rowCount(); } /** * Iniciar transacción */ public function beginTransaction(): bool { if ($this->transactionLevel === 0) { $this->pdo->beginTransaction(); } else { $this->pdo->exec("SAVEPOINT level_{$this->transactionLevel}"); } $this->transactionLevel++; return true; } /** * Confirmar transacción */ public function commit(): bool { $this->transactionLevel--; if ($this->transactionLevel === 0) { return $this->pdo->commit(); } return true; } /** * Revertir transacción */ public function rollback(): bool { $this->transactionLevel--; if ($this->transactionLevel === 0) { return $this->pdo->rollBack(); } $this->pdo->exec("ROLLBACK TO SAVEPOINT level_{$this->transactionLevel}"); return true; } /** * Obtener último ID insertado */ public function lastInsertId(): int { return (int) $this->pdo->lastInsertId(); } /** * Traducir PDOException a DatabaseException con mensaje seguro */ private function translatePdoException(PDOException $e): DatabaseException { $errorInfo = $e->errorInfo ?? []; $mysqlCode = (string)($errorInfo[1] ?? ''); $constraintName = ''; // Extraer nombre de constraint si existe if (preg_match("/for key '([^']+)'/", $e->getMessage(), $matches)) { $constraintName = $matches[1]; } $safeMessage = match($mysqlCode) { '1062' => 'Ya existe un registro con estos datos', '1452' => 'Referencia a registro inexistente', '1451' => 'No se puede eliminar: existen registros relacionados', '1048' => 'Faltan campos obligatorios', '1406' => 'Uno de los campos excede la longitud permitida', '1264' => 'Valor fuera de rango permitido', '1366' => 'Valor incorrecto para el tipo de campo', '2002', '2003', '2006' => 'Error de conexion con la base de datos', '1213' => 'Conflicto de concurrencia, intente de nuevo', '1205' => 'Tiempo de espera agotado, intente de nuevo', default => 'Error interno de base de datos' }; $httpCode = match($mysqlCode) { '1062' => 409, '1452', '1451' => 422, '1048', '1406', '1264', '1366' => 400, '2002', '2003', '2006' => 503, '1213', '1205' => 503, default => 500 }; return new DatabaseException($safeMessage, $httpCode, $e, $mysqlCode, $constraintName); } private function __clone() {} public function __wakeup() { throw new DatabaseException('No serializable'); } }
Save
cmd:
run