Практическое руководство по PHP скриптам для 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".