Страница сайта на PHP, работающая с базой данных
Основные методы интеграции базы данных в PHP-страницу
Как выполнить безопасное взаимодействие с базой данных через PDO?
PDO обеспечивает универсальный доступ к разным СУБД и поддержку подготовленных выражений. Цель - создать защищенное от SQL-инъекций соединение с возможностью легкой смены драйвера.
<?php
$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';
$user = 'root';
$pass = 'password';
try {
$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('SELECT name, email FROM users WHERE id = :id');
$stmt->execute(['id' => 1]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo 'Имя: ' . $row['name'] . ', Email: ' . $row['email'];
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>
Имя: Иван, Email: ivan@example.com
Типичные ошибки:
- Отсутствие драйвера PDO для MySQL (выброс исключения "could not find driver"). Решение: установить расширение php_pdo_mysql.
- Неверные учетные данные (код 1045). Проверять параметры подключения.
- Игнорирование исключений: без установки ATTR_ERRMODE ошибки остаются незамеченными.
- Неправильное экранирование: использование подготовленных выражений обязательно, конкатенация чревата инъекциями.
Как использовать MySQLi в объектно-ориентированном стиле?
Подходит для проектов, где гарантированно используется только MySQL. Случаи использования: миграция с mysql_* на современный API, работа с асинхронными запросами (через poll).
<?php
$mysqli = new mysqli('localhost', 'root', 'password', 'testdb');
if ($mysqli->connect_error) {
die('Ошибка подключения: ' . $mysqli->connect_error);
}
$stmt = $mysqli->prepare('SELECT name FROM users WHERE id = ?');
$stmt->bind_param('i', $id);
$id = 2;
$stmt->execute();
$stmt->bind_result($name);
$stmt->fetch();
echo 'Имя: ' . $name;
$stmt->close();
$mysqli->close();
?>
Имя: Мария
Проблемы: привязка параметров только по позиции (?, ?). Переключение на другую СУБД потребует замены кода. Обработка ошибок через методы error* (исключения не выбрасываются по умолчанию).
Почему устаревшие функции mysql_* опасны?
Удалены в PHP 7, не имеют поддержки подготовленных выражений, открыты для SQL-инъекций. Случаи использования: только поддержка легаси-кода до миграции.
// НЕ ИСПОЛЬЗОВАТЬ
$link = mysql_connect('localhost', 'root', 'password') or die(mysql_error());
mysql_select_db('testdb', $link);
$id = $_GET['id']; // инъекция
$result = mysql_query("SELECT * FROM users WHERE id = $id");
Результат: злоумышленник может вставить ' OR '1'='1', получив доступ ко всем записям. Функции удалены начиная с PHP 7.0, код не будет работать в современных версиях.
Как упростить запросы через ORM (illuminate/database)?
Библиотека Eloquent (из Laravel) может использоваться вне фреймворка. Цель: абстрагировать SQL, использовать объекты и отношения.
<?php
require 'vendor/autoload.php';
use Illuminate\Database\Capsule\Manager as Capsule;
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'testdb',
'username' => 'root',
'password' => 'password',
'charset' => 'utf8mb4',
]);
$capsule->bootEloquent();
$users = Capsule::table('users')->where('id', '>', 1)->get();
foreach ($users as $user) {
echo $user->name . ' ' . $user->email . '<br>';
}
?>
Мария maria@example.com Петр petr@example.com
Проблемы: требуется установка composer и дополнительная настройка. При малых проектах излишняя сложность. Необходимо знание синтаксиса Fluent.
Расширенные примеры работы с базой данных
Транзакции в 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();
echo 'Перевод выполнен успешно';
} catch (Exception $e) {
$pdo->rollBack();
echo 'Ошибка: ' . $e->getMessage();
}
?>
Перевод выполнен успешно (при отсутствии ошибок)
Именованные плейсхолдеры в сложных запросах
<?php
$sql = 'SELECT * FROM orders WHERE (status = :status OR :status IS NULL) AND total > :min';
$stmt = $pdo->prepare($sql);
$stmt->execute([
'status' => 'active',
'min' => 100
]);
$orders = $stmt->fetchAll();
?>
Пакетная вставка через подготовленные выражения
<?php
$stmt = $pdo->prepare('INSERT INTO logs (message, created_at) VALUES (:msg, NOW())');
$messages = ['Ошибка 1', 'Ошибка 2', 'Предупреждение'];
foreach ($messages as $msg) {
$stmt->execute(['msg' => $msg]);
}
echo 'Вставлено ' . count($messages) . ' записей';
?>
Вставлено 3 записей
Получение данных в виде объекта с помощью fetchObject
<?php
$stmt = $pdo->query('SELECT id, name FROM users');
$stmt->setFetchMode(PDO::FETCH_OBJ);
while ($user = $stmt->fetch()) {
echo $user->id . ': ' . $user->name . '<br>';
}
?>
1: Иван
2: Мария
Установка кодировки через MYSQL_ATTR_INIT_COMMAND
<?php
$dsn = 'mysql:host=localhost;dbname=testdb';
$options = [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
$pdo = new PDO($dsn, 'root', 'password', $options);
?>
Позволяет задать кодировку сразу после соединения, без отдельного вызова query.
Обработка всех ошибок через try-catch с PDOException
<?php
try {
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// любые запросы
} catch (PDOException $e) {
error_log('DB error: ' . $e->getMessage());
echo 'Внутренняя ошибка, попробуйте позже';
}
?>
Unbuffered query для больших данных
<?php
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$stmt = $pdo->query('SELECT * FROM huge_table');
while ($row = $stmt->fetch()) {
// обработка без загрузки всего набора в память
}
?>
Риск: во время итерации нельзя выполнять другие запросы к тому же соединению.
Вызов хранимой процедуры через PDO
<?php
$stmt = $pdo->prepare('CALL get_user(:id)');
$stmt->execute(['id' => 1]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
?>