Практическое руководство по PHP скриптам для MySQL

Раздел: Базы данных -> MySQL

Основы создания PHP скриптов для MySQL

Наиболее эффективное решение: использование PDO (PHP Data Objects)

PDO предоставляет единый интерфейс для работы с различными СУБД, включая MySQL. Ключевые преимущества: автоматическая защита от SQL-инъекций через подготовленные запросы, удобная обработка ошибок через исключения, поддержка транзакций.

Пример подключения к MySQL через PDO:

$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'username';
$password = 'password';
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false,
];
try {
    $pdo = new PDO($dsn, $user, $password, $options);
} catch (PDOException $e) {
    die('Ошибка подключения: ' . $e->getMessage());
}

Php mysql скрипты (php mysql скрипты)

Пояснение: в DSN указывается хост, имя базы данных, кодировка. Параметр ATTR_ERRMODE устанавливает режим генерации исключений. Отключение эмуляции подготовленных запросов (ATTR_EMULATE_PREPARES = false) обеспечивает использование настоящих prepared statements на стороне MySQL.

Выполнение SELECT запроса с параметрами:

$stmt = $pdo->prepare('SELECT id, name, email FROM users WHERE status = :status');
$stmt->execute([':status' => 'active']);
$users = $stmt->fetchAll();
foreach ($users as $user) {
    echo $user['name'] . ' - ' . $user['email'] . "\n";
}

Вставка данных:

$stmt = $pdo->prepare('INSERT INTO users (name, email, status) VALUES (:name, :email, :status)');
$stmt->execute([
    ':name' => 'Иван Иванов',
    ':email' => 'ivan@example.com',
    ':status' => 'active'
]);

Обновление и удаление выполняются аналогично через prepared statements.

Типичные проблемы и их решения:

  • Проблема: Ошибка подключения "could not find driver". Решение: Установить расширение php_pdo_mysql в php.ini.
  • Проблема: SQL-инъекция при использовании прямой подстановки переменных. Решение: Всегда использовать подготовленные запросы с плейсхолдерами.
  • Проблема: Исключение PDOException не перехватывается. Решение: Оборачивать операции в try-catch блоки.

Как выполнить несколько запросов в одной транзакции с PDO?

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

try {
    $pdo->beginTransaction();
    $pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
    $pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    echo 'Ошибка транзакции: ' . $e->getMessage();
}

Важно: после commit() изменения становятся постоянными. Если происходит исключение, rollBack() откатывает все изменения.

Возможные проблемы:

  • Проблема: Вложенные транзакции не поддерживаются PDO (только одна активная). Решение: Использовать точки сохранения (SAVEPOINT) при необходимости имитации вложенности.
  • Проблема: Автоматический откат при ошибке, если не вызван rollBack. Решение: Всегда обрабатывать исключения и вызывать rollBack.

Как использовать MySQLi для запросов?

MySQLi предоставляет как процедурный, так и объектно-ориентированный интерфейс. Пример объектно-ориентированного подхода:

$mysqli = new mysqli('localhost', 'username', 'password', 'testdb');
if ($mysqli->connect_error) {
    die('Ошибка подключения: ' . $mysqli->connect_error);
}
$stmt = $mysqli->prepare('SELECT id, name FROM users WHERE status = ?');
$status = 'active';
$stmt->bind_param('s', $status);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    echo $row['name'] . "\n";
}
$stmt->close();
$mysqli->close();

Процедурный стиль использует функции mysqli_*.

Распространенные ошибки:

  • Проблема: Неверный порядок bind_param: типы должны соответствовать параметрам. Решение: Использовать строку типов (i - integer, d - double, s - string, b - blob).
  • Проблема: Не освобождается результат (memory leak). Решение: Вызывать $stmt->free_result() или $result->free().

Как вызвать хранимую процедуру MySQL из PHP?

Хранимые процедуры позволяют инкапсулировать бизнес-логику на стороне сервера. Пример процедуры с входным и выходным параметром:

$stmt = $pdo->prepare('CALL GetUserCount(:status, @total)');
$stmt->execute([':status' => 'active']);
$stmt->closeCursor();
$result = $pdo->query('SELECT @total AS count')->fetch();
echo 'Количество активных пользователей: ' . $result['count'];

Для сложных процедур, возвращающих несколько результирующих наборов, необходимо использовать nextRowset().

Проблемы при работе с процедурами:

  • Проблема: Ошибка "OUT or INOUT argument is not a variable". Решение: Использовать пользовательские переменные (@var) для выходных параметров.
  • Проблема: Нельзя получить результат после вызова без closeCursor. Решение: Всегда закрывать курсор перед следующим запросом.

Как обрабатывать большие наборы данных без переполнения памяти?

По умолчанию PDO использует буферизованные запросы, загружающие весь результат в память. Для потоковой обработки используется небуферизованный режим:

$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->query('SELECT * FROM large_table');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    processRow($row);
}
$stmt->closeCursor();
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);

В MySQLi для небуферизованных запросов используется MYSQLI_USE_RESULT.

Возможные сложности:

  • Проблема: При небуферизованном запросе нельзя выполнять другие запросы, пока не закончится выборка. Решение: Освобождать результат через closeCursor() или free().
  • Проблема: Ошибка "Commands out of sync". Решение: Убедиться, что все предыдущие результаты обработаны.

Расширенные примеры PHP MySQL скриптов

Пример 1: Класс-обёртка для работы с БД через PDO

Представленный класс реализует базовые операции с использованием подготовленных запросов и транзакций.

Пример
class Database {
    private static ?PDO $instance = null;
    private static array $config = [
        'host' => 'localhost',
        'dbname' => 'testdb',
        'user' => 'username',
        'password' => 'password',
        'charset' => 'utf8mb4'
    ];

    public static function getInstance(): PDO {
        if (self::$instance === null) {
            $dsn = 'mysql:host=' . self::$config['host'] . ';dbname=' . self::$config['dbname'] . ';charset=' . self::$config['charset'];
            self::$instance = new PDO($dsn, self::$config['user'], self::$config['password'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ]);
        }
        return self::$instance;
    }

    public static function query(string $sql, array $params = []): PDOStatement {
        $stmt = self::getInstance()->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    public static function fetchAll(string $sql, array $params = []): array {
        return self::query($sql, $params)->fetchAll();
    }

    public static function fetchOne(string $sql, array $params = []): ?array {
        $row = self::query($sql, $params)->fetch();
        return $row ?: null;
    }

    public static function insert(string $table, array $data): int {
        $columns = implode(', ', array_keys($data));
        $placeholders = ':' . implode(', :', array_keys($data));
        $sql = "INSERT INTO $table ($columns) VALUES ($placeholders)";
        self::query($sql, $data);
        return (int) self::getInstance()->lastInsertId();
    }

    public static function update(string $table, array $data, string $where, array $whereParams = []): int {
        $set = implode(', ', array_map(fn($col) => "$col = :$col", array_keys($data)));
        $sql = "UPDATE $table SET $set WHERE $where";
        $params = array_merge($data, $whereParams);
        $stmt = self::query($sql, $params);
        return $stmt->rowCount();
    }

    public static function delete(string $table, string $where, array $params = []): int {
        $sql = "DELETE FROM $table WHERE $where";
        $stmt = self::query($sql, $params);
        return $stmt->rowCount();
    }
}

$users = Database::fetchAll('SELECT * FROM users WHERE status = :status', [':status' => 'active']);
$newId = Database::insert('users', ['name' => 'Петр Петров', 'email' => 'petr@example.com', 'status' => 'active']);
Database::update('users', ['status' => 'inactive'], 'id = :id', [':id' => $newId]);
echo 'Затронуто строк: ' . Database::delete('users', 'status = :status', [':status' => 'inactive']);
// Результат: (при наличии данных) массив активных пользователей, новый ID, 1 строка обновлена, количество удаленных.

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

Пример 2: Сложный запрос с JOIN, группировкой и фильтрацией

Требуется получить список заказов с информацией о клиентах и суммой заказа для тех, у кого сумма превышает 1000.

Пример
$sql = "SELECT c.name, c.email, SUM(o.total) AS total_spent
        FROM customers c
        LEFT JOIN orders o ON c.id = o.customer_id
        WHERE c.status = :status
        GROUP BY c.id
        HAVING total_spent > :min_total
        ORDER BY total_spent DESC";
$params = [':status' => 'active', ':min_total' => 1000];
$result = Database::fetchAll($sql, $params);
foreach ($result as $row) {
    echo "{$row['name']} ({$row['email']}) - {$row['total_spent']} руб.\n";
}
Иванов Иван (ivan@example.com) - 4500 руб.
Петрова Анна (anna@example.com) - 2300 руб.

Пояснение: LEFT JOIN гарантирует включение клиентов без заказов (сумма NULL преобразуется в 0, но они не попадут в HAVING). Группировка по c.id, условие HAVING применяется после группировки.

Пример 3: Вызов хранимой процедуры с несколькими результирующими наборами

Процедура возвращает два набора: список пользователей и общую статистику.

Пример
$pdo = Database::getInstance();
$stmt = $pdo->prepare('CALL GetUsersAndStats(:status)');
$stmt->execute([':status' => 'active']);

$users = $stmt->fetchAll();
echo "Пользователи:\n";
foreach ($users as $user) {
    echo $user['name'] . "\n";
}

$stmt->nextRowset();
$stats = $stmt->fetchAll();
echo "Статистика:\n";
foreach ($stats as $stat) {
    echo "Всего выбрано: " . $stat['total'] . "\n";
}
$stmt->closeCursor();
Пользователи:
Иван
Мария
Статистика:
Всего выбрано: 2

Пояснение: nextRowset() позволяет переключаться между наборами. Важно обработать каждый набор, иначе может возникнуть ошибка "Commands out of sync".

PHP MySQL скрипты - comments

En
Php mysql скрипты (php)