Разработка классов-помощников для MySQL в PHP
Классы для работы с MySQL в PHP: основные подходы
Как построить класс для безопасного выполнения SQL запросов с использованием PDO?
Наиболее эффективное решение основано на расширении PDO. Класс инкапсулирует соединение и предоставляет методы для типовых операций: выборка, вставка, обновление, удаление. Использование подготовленных выражений гарантирует защиту от SQL-инъекций.
class Database {
private PDO $pdo;
public function __construct(array $config) {
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4";
$this->pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
public function query(string $sql, array $params = []): PDOStatement {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
public function fetchAll(string $sql, array $params = []): array {
return $this->query($sql, $params)->fetchAll();
}
public function fetchOne(string $sql, array $params = []): ?array {
$row = $this->query($sql, $params)->fetch();
return $row ?: null;
}
public function insert(string $table, array $data): int {
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO $table ($columns) VALUES ($placeholders)";
$this->query($sql, array_values($data));
return (int) $this->pdo->lastInsertId();
}
public function update(string $table, array $data, string $where, array $whereParams = []): int {
$set = implode(', ', array_map(fn($col) => "$col = ?", array_keys($data)));
$sql = "UPDATE $table SET $set WHERE $where";
$stmt = $this->query($sql, array_merge(array_values($data), $whereParams));
return $stmt->rowCount();
}
public function delete(string $table, string $where, array $whereParams = []): int {
$sql = "DELETE FROM $table WHERE $where";
$stmt = $this->query($sql, $whereParams);
return $stmt->rowCount();
}
}
Engine classes mysql php (классы для работы с mysql в php)
Пример использования:
$db = new Database([
'host' => 'localhost',
'dbname' => 'test',
'user' => 'root',
'pass' => ''
]);
$users = $db->fetchAll('SELECT * FROM users WHERE age > ?', [18]);
$user = $db->fetchOne('SELECT * FROM users WHERE id = ?', [1]);
$newId = $db->insert('users', ['name' => 'Иван', 'age' => 25]);
$db->update('users', ['age' => 26], 'id = ?', [1]);
$db->delete('users', 'id = ?', [1]);
Как реализовать единый доступ к базе данных через синглтон?
Синглтон гарантирует, что в приложении существует только один экземпляр класса Database. Это удобно для небольших проектов, где не требуется несколько соединений.
class DatabaseSingleton {
private static ?self $instance = null;
private PDO $pdo;
private function __construct(array $config) {
// аналогично Database
}
public static function getInstance(array $config = []): self {
if (self::$instance === null) {
self::$instance = new self($config);
}
return self::$instance;
}
// методы query, fetchAll и т.д. такие же
}
Использование:
$db = DatabaseSingleton::getInstance($config);
$users = $db->fetchAll('SELECT * FROM users');
Как обернуть mysqli в класс?
Для проектов, где PDO недоступен (устаревшие версии PHP), можно создать класс на основе mysqli. Реализация требует внимания к подготовленным выражениям и обработке ошибок.
class MySQLiWrapper {
private mysqli $mysqli;
public function __construct(array $config) {
$this->mysqli = new mysqli($config['host'], $config['user'], $config['pass'], $config['dbname']);
if ($this->mysqli->connect_errno) {
throw new RuntimeException('Ошибка подключения: ' . $this->mysqli->connect_error);
}
$this->mysqli->set_charset('utf8mb4');
}
public function query(string $sql, array $params = []): mysqli_stmt {
$stmt = $this->mysqli->prepare($sql);
if ($stmt === false) {
throw new RuntimeException('Ошибка подготовки: ' . $this->mysqli->error);
}
if (!empty($params)) {
$types = str_repeat('s', count($params)); // упрощенно: все строки
$stmt->bind_param($types, ...$params);
}
$stmt->execute();
return $stmt;
}
public function fetchAll(string $sql, array $params = []): array {
$stmt = $this->query($sql, $params);
$result = $stmt->get_result();
return $result->fetch_all(MYSQLI_ASSOC);
}
// остальные методы аналогично
}
Недостаток: связывание параметров требует указания типов, что усложняет универсальность. При передаче большого количества параметров можно использовать отражение для определения типов.
Как применить паттерн Репозиторий для отделения логики запросов?
Репозиторий абстрагирует работу с конкретной таблицей, предоставляя методы, соответствующие предметной области. Класс Database используется внутри репозитория как низкоуровневое API.
class UserRepository {
private Database $db;
public function __construct(Database $db) {
$this->db = $db;
}
public function findById(int $id): ?array {
return $this->db->fetchOne('SELECT * FROM users WHERE id = ?', [$id]);
}
public function findActive(): array {
return $this->db->fetchAll('SELECT * FROM users WHERE status = ?', ['active']);
}
public function save(array $user): int {
if (isset($user['id'])) {
$this->db->update('users', $user, 'id = ?', [$user['id']]);
return $user['id'];
} else {
return $this->db->insert('users', $user);
}
}
public function remove(int $id): void {
$this->db->delete('users', 'id = ?', [$id]);
}
}
Использование:
$db = new Database($config);
$userRepo = new UserRepository($db);
$user = $userRepo->findById(42);
$userRepo->save(['name' => 'Новый пользователь', 'age' => 30]);
Расширенные примеры использования классов MySQL
Как выполнять транзакции с точками сохранения?
$db->beginTransaction();
$db->query('UPDATE accounts SET balance = balance - 100 WHERE id = ?', [1]);
$db->query('UPDATE accounts SET balance = balance + 100 WHERE id = ?', [2]);
$db->commit();
Транзакция завершена. Если возникает исключение, автоматический откат через PDO::ERRMODE_EXCEPTION.
Как реализовать массовую вставку (bulk insert) с повторным использованием подготовленного выражения?
$stmt = $db->getPdo()->prepare('INSERT INTO logs (message, level) VALUES (?, ?)');
$logs = [
['Старт', 'info'],
['Ошибка', 'error'],
['Завершение', 'info'],
];
foreach ($logs as $log) {
$stmt->execute($log);
}
echo 'Вставлено ' . count($logs) . ' записей';
Вставлено 3 записей
Как использовать именованные плейсхолдеры для сложных запросов?
$sql = 'SELECT * FROM orders WHERE user_id = :uid AND status = :status ORDER BY created_at DESC LIMIT :limit';
$orders = $db->fetchAll($sql, ['uid' => 10, 'status' => 'pending', 'limit' => 5]);
Вернёт массив заказов с указанными параметрами. Именованные плейсхолдеры повышают читаемость.
Как обработать NULL значения в параметрах?
$db->insert('users', ['name' => 'Петр', 'email' => null]);
// PDO корректно передаёт NULL. В SQL значение будет NULL.
Как получить затронутые строки после UPDATE?
$affected = $db->update('products', ['price' => 99.99], 'category_id = ?', [5]);
echo "Обновлено продуктов: $affected";
Как выполнить запрос с JOIN и группировкой?
$sql = 'SELECT u.name, COUNT(o.id) as orders_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = :status
GROUP BY u.id
HAVING orders_count > :min';
$result = $db->fetchAll($sql, ['status' => 'active', 'min' => 0]);
Вернёт массив с именем пользователя и количеством заказов.
Как обрабатывать ошибки с кастомными исключениями?
class DatabaseException extends RuntimeException {}
// В классе Database:
public function __construct(array $config) {
try {
// ...
} catch (PDOException $e) {
throw new DatabaseException('Не удалось подключиться к БД: ' . $e->getMessage(), 0, $e);
}
}
Как использовать несколько соединений в одном приложении?
$master = new Database($masterConfig);
$slave = new Database($slaveConfig);
// Запись только на мастер, чтение с обоих
$users = $slave->fetchAll('SELECT * FROM users');
$master->insert('logs', ['action' => 'read']);
Как реализовать пагинацию с LIMIT и OFFSET?
$page = 2;
$perPage = 10;
$offset = ($page - 1) * $perPage;
$users = $db->fetchAll('SELECT * FROM users LIMIT ? OFFSET ?', [$perPage, $offset]);
Как сохранить результат запроса в файл?
$stmt = $db->query('SELECT * FROM large_table');
$file = fopen('export.csv', 'w');
while ($row = $stmt->fetch()) {
fputcsv($file, $row);
}
fclose($file);
Создан файл export.csv с данными без загрузки всего результата в память.