Разработка собственного класса SQL для PHP

Раздел: Базы данных -> Работа с базами данных SQL в PHP

Создание класса для работы с SQL в PHP

Для структурированного и безопасного взаимодействия с базами данных в PHP часто используют собственные классы-обёртки. Такой подход позволяет инкапсулировать логику соединения, подготовку запросов, обработку ошибок и повторное использование кода. Ниже рассмотрен наиболее эффективный вариант на основе PDO, а также альтернативные решения.

Основное решение: класс-обёртка для PDO

Как создать универсальный класс для работы с MySQL через PDO с поддержкой подготовленных запросов и транзакций?

Класс DB использует PDO для соединения, выполнение запросов через prepare/execute, управление транзакциями и обработку исключений. Подходит для проектов любого размера.

class DB {
    private static ?PDO $pdo = null;

    public static function connect(string $dsn, string $user, string $pass): void {
        try {
            self::$pdo = new PDO($dsn, $user, $pass, [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
        } catch (PDOException $e) {
            throw new RuntimeException('Ошибка подключения: ' . $e->getMessage());
        }
    }

    public static function query(string $sql, array $params = []): PDOStatement {
        if (!self::$pdo) {
            throw new LogicException('Соединение не установлено. Вызовите DB::connect()');
        }
        $stmt = self::$pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    // Дополнительные методы: fetch, fetchAll, beginTransaction, commit, rollBack
}

Php sql insert (insert в php)

Пояснения: Статическое свойство хранит единственный экземпляр PDO. Метод connect настраивает режим ошибок и способ выборки. query принимает SQL и массив параметров, возвращает объект PDOStatement.

Типичные ошибки:

  • Повторное создание PDO без проверки приводит к лишним подключениям. Решение - синглтон или проверка на null.
  • Необработанные исключения при ошибках SQL. Включите ERRMODE_EXCEPTION и используйте try/catch.
  • Неправильный DSN для MySQL: mysql:host=localhost;dbname=test;charset=utf8mb4.

Цели использования: создание безопасных запросов без ручного экранирования, работа с разными СУБД через PDO, упрощение кода при многократных запросах.

Альтернатива 1: класс на основе mysqli

Как написать простой класс для MySQL с процедурным стилем без PDO?

Для проектов, где используется только MySQL, можно обойтись встроенным расширением mysqli.

class MySQL {
    private mysqli $conn;

    public function __construct(string $host, string $user, string $pass, string $dbname) {
        $this->conn = new mysqli($host, $user, $pass, $dbname);
        if ($this->conn->connect_error) {
            throw new RuntimeException('Ошибка подключения: ' . $this->conn->connect_error);
        }
        $this->conn->set_charset('utf8mb4');
    }

    public function exec(string $sql, string $types = '', array $params = []): mysqli_stmt | bool {
        $stmt = $this->conn->prepare($sql);
        if ($stmt === false) {
            throw new RuntimeException('Ошибка подготовки: ' . $this->conn->error);
        }
        if ($types) {
            $stmt->bind_param($types, ...$params);
        }
        $stmt->execute();
        return $stmt;
    }
}

Sql where php (условие where в sql-запросах php)

Пояснения: Привязка параметров требует указания типов (s, i, d). Метод exec возвращает подготовленный запрос, из которого можно получить результат через get_result().

Проблемы: требуется явно указывать типы параметров, нет встроенного переключения между СУБД, сложнее управлять транзакциями.

Альтернатива 2: прямое использование PDO без класса-обёртки

Когда целесообразно обойтись без собственного класса и использовать PDO напрямую в контроллерах?

Для небольших скриптов или когда фреймворк уже предоставляет свою абстракцию.

$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
$user = $stmt->fetch();

Sql инъекция php (sql-инъекции в php)

Пояснения: Простая инициализация, отсутствие дополнительных слоёв. Минус - дублирование кода подключения и обработки ошибок в каждом месте.

Риски: легко забыть установить режим ошибок, трудно поддерживать при росте проекта.

Альтернатива 3: использование ORM (например, Eloquent)

Какие преимущества даёт полноценная ORM вместо самодельного класса?

Для крупных проектов, где требуется работа с объектами и сложными связями.

// Composer: illuminate/database
$capsule = new Illuminate\Database\Capsule\Manager;
$capsule->addConnection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'test',
    'username' => 'root',
    'password' => '',
]);
$capsule->setAsGlobal();
$users = User::where('active', 1)->get();

Пояснения: Активная запись, гидратация объектов, миграции. Требует установки через Composer и настройки автозагрузки.

Недостатки: избыточность для простых задач, снижение производительности на больших выборках, жёсткая привязка к ORM.

- Php class sql (класс для работы с sql в php)
- Php sql connect (подключение к sql в php)
- Php ms sql (работа с ms sql в php)

Расширенные примеры работы с классом DB

Ниже приведены нестандартные сценарии использования собственного класса для SQL.

Пример 1: Транзакция с множественными вставками и откатом

Пример
DB::beginTransaction();
try {
    DB::query('INSERT INTO log (message) VALUES (?)', ['Старт']);
    foreach ($items as $item) {
        DB::query('INSERT INTO items (name, price) VALUES (?, ?)', [$item['name'], $item['price']]);
    }
    DB::commit();
} catch (Exception $e) {
    DB::rollBack();
    error_log('Транзакция отменена: ' . $e->getMessage());
    throw $e;
}
// При ошибке ни одна из вставок не сохраняется. При успехе все записи фиксируются.

Пояснение: Использование beginTransaction/commit/rollBack гарантирует атомарность.

Пример 2: Получение одной записи в виде объекта

Пример
$stmt = DB::query('SELECT * FROM users WHERE id = ?', [1]);
$user = $stmt->fetch(PDO::FETCH_OBJ);
echo $user->name;
Иван

Пояснение: Изменение режима выборки через параметр fetch позволяет получать данные в нужном формате.

Пример 3: Использование хранимой процедуры с выходными параметрами (MySQL)

Пример
// Процедура: CREATE PROCEDURE sp_get_user_count(OUT total INT) ...
$stmt = DB::query('CALL sp_get_user_count(@total)');
$stmt->closeCursor();
$stmt = DB::query('SELECT @total AS total');
$row = $stmt->fetch();
echo 'Всего пользователей: ' . $row['total'];
Всего пользователей: 42

Пояснение: Для работы с выходными переменными необходим отдельный запрос SELECT.

Пример 4: Bulk insert через один запрос

Пример
$data = [
    ['name' => 'A', 'age' => 20],
    ['name' => 'B', 'age' => 25],
];
$placeholders = implode(',', array_fill(0, count($data), '(?,?)'));
$params = [];
foreach ($data as $row) {
    $params[] = $row['name'];
    $params[] = $row['age'];
}
$sql = "INSERT INTO users (name, age) VALUES $placeholders";
DB::query($sql, $params);
// Вставлено 2 строки за один вызов prepare/execute

Пояснение: Подготовка одного запроса с несколькими наборами значений ускоряет массовую вставку.

Пример 5: Обработка ошибок с пользовательским исключением

Пример
class DatabaseException extends RuntimeException {}

try {
    DB::query('SELECT * FROM non_existent_table');
} catch (PDOException $e) {
    throw new DatabaseException('Ошибка базы данных: ' . $e->getMessage(), 0, $e);
}
// Выбрасывается DatabaseException с подробным сообщением

Пояснение: Обёртка исключений позволяет унифицировать обработку в приложении.

Класс для работы с SQL в PHP - comments

En
Php class sql (php)