PHP и MySQL для начинающих: от подключения до выборки данных
Основы работы с PHP и MySQL: варианты подключения и безопасные запросы
Как эффективно соединиться с MySQL и выполнять запросы с защитой от SQL-инъекций?
Рекомендуемый подход: использование PDO (PHP Data Objects) с подготовленными выражениями. Это универсальный интерфейс, поддерживающий множество СУБД и обеспечивающий безопасную передачу параметров.
Пример базового подключения и запроса SELECT:
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$pass = '';
try {
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE age > :age');
$stmt->execute(['age' => 18]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($users);
} catch (PDOException $e) {
error_log('Database error: ' . $e->getMessage());
}
?>
Код подключается к базе testdb, устанавливает режим исключений для обработки ошибок, подготавливает запрос с именованным параметром :age и выполняет его с переданным значением. Результат получается в виде ассоциативного массива.
Типичные проблемы:
- Неверный DSN (например, пропущен порт или кодировка) - выбрасывается PDOException.
- Отсутствие драйвера PDO для MySQL - проверяется функция extension_loaded('pdo_mysql').
- Утечка данных при выводе ошибок в production - рекомендуется логировать, а не выводить сообщения.
Как подключиться к MySQL через MySQLi (процедурный стиль)?
MySQLi (MySQL Improved) - расширение, ориентированное только на MySQL. Процедурный вариант удобен для быстрых скриптов.
<?php
$conn = mysqli_connect('localhost', 'root', '', 'testdb');
if (!$conn) {
die('Connect error: ' . mysqli_connect_error());
}
$result = mysqli_query($conn, "SELECT * FROM users WHERE age > 18");
if ($result) {
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'] . '<br>';
}
mysqli_free_result($result);
}
mysqli_close($conn);
?>
Здесь mysqli_connect возвращает объект соединения, а запрос выполняется напрямую. Важно: такой код уязвим для SQL-инъекций, если в запрос подставлять пользовательские данные без экранирования (функция mysqli_real_escape_string).
Ошибки:
- При неправильных учётных данных mysqli_connect() возвращает false, но соединение при этом может быть частично создано - проверять следует через mysqli_connect_errno().
- Забыть освободить результат (mysqli_free_result) - может привести к утечке памяти.
Как использовать MySQLi в объектно-ориентированном стиле?
Объектная версия MySQLi выглядит компактнее и допускает цепочки вызовов.
<?php
$mysqli = new mysqli('localhost', 'root', '', 'testdb');
if ($mysqli->connect_errno) {
die('Connect error: ' . $mysqli->connect_error);
}
$stmt = $mysqli->prepare('SELECT name, email FROM users WHERE id = ?');
$stmt->bind_param('i', $userId);
$userId = 5;
$stmt->execute();
$stmt->bind_result($name, $email);
$stmt->fetch();
echo "$name - $email";
$stmt->close();
$mysqli->close();
?>
Метод prepare возвращает объект statement, параметры связываются через bind_param (первый аргумент - типы: i - integer, s - string и т.д.). Затем результат извлекается в переменные.
Сложности:
- Привязка параметров требует точного соответствия типов, иначе запрос может вернуть пустой результат.
- После bind_result нельзя использовать fetchAll - только построчное чтение.
Какие ещё способы обработки ошибок существуют?
Кроме исключений PDO и проверки возвращаемых значений, можно использовать пользовательские функции обработки ошибок или механизм error_reporting для вывода предупреждений на этапе разработки.
<?php
// PDO без исключений
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$stmt = $pdo->prepare('INVALID SQL');
$stmt->execute(); // только предупреждение, скрипт продолжается
?>
Проблема:
Режим предупреждений может скрыть серьёзные ошибки. Для production лучше использовать исключения или логирование.
Расширенные примеры работы с PHP и MySQL
1. Создание таблицы и вставка данных с подготовленным выражением (PDO)
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec('CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
$stmt = $pdo->prepare('INSERT INTO products (title, price) VALUES (:title, :price)');
$products = [
['title' => 'Мышка', 'price' => 15.50],
['title' => 'Клавиатура', 'price' => 45.00],
];
foreach ($products as $item) {
$stmt->execute($item);
}
echo 'Добавлено записей: ' . count($products);
?>
Добавлено записей: 2
2. Выборка с JOIN (PDO)
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$sql = 'SELECT o.id, o.total, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = :status
ORDER BY o.created_at DESC
LIMIT :limit';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':status', 'paid', PDO::PARAM_STR);
$stmt->bindValue(':limit', 10, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($result);
?>
Array
(
[0] => Array
(
[id] => 101
[total] => 235.00
[name] => Иванов
)
[1] => Array
(
[id] => 102
[total] => 87.50
[name] => Петрова
)
)
3. Транзакции с откатом (PDO)
<?php
$pdo->beginTransaction();
try {
$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();
error_log('Transaction failed: ' . $e->getMessage());
}
?>
(без вывода, при успехе – данные обновлены, при ошибке – откат к исходному состоянию)
4. Использование placeholders и like в MySQLi (объектный стиль)
<?php
$mysqli = new mysqli('localhost', 'root', '', 'test');
$search = '%клав%';
$stmt = $mysqli->prepare('SELECT title FROM products WHERE title LIKE ?');
$stmt->bind_param('s', $search);
$stmt->execute();
$stmt->bind_result($title);
while ($stmt->fetch()) {
echo $title . '<br>';
}
$stmt->close();
$mysqli->close();
?>
Клавиатура
5. Обновление и удаление с проверкой количества затронутых строк
<?php
$pdo->exec("UPDATE products SET price = price * 1.1 WHERE id < 100");
echo 'Обновлено строк: ' . $pdo->rowCount();
$stmt = $pdo->prepare('DELETE FROM products WHERE price = 0');
$stmt->execute();
echo 'Удалено строк: ' . $stmt->rowCount();
?>
Обновлено строк: 3 Удалено строк: 0