Создание и администрирование учётных записей в PHP с поддержкой баз данных

Раздел: PHP программирование -> Базы данных в PHP

Основные подходы к хранению пользователей в PHP

Как реализовать базу пользователей с помощью PDO и MySQL?

Цель

Создать надёжное хранилище учётных записей с защитой от SQL-инъекций, поддержкой разных СУБД и удобным кодом. Этот подход считается наиболее эффективным для веб-проектов.

Подготовка таблицы


CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  

Таблица содержит уникальные логин и email, хэш пароля и метку времени регистрации.

Регистрация пользователя


<?php
$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8', 'user', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$username = $_POST['username'];
$email = $_POST['email'];
$password = $_POST['password'];

$hash = password_hash($password, PASSWORD_DEFAULT);

$stmt = $pdo->prepare('INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)');
$stmt->execute([$username, $email, $hash]);
?>
  

Подготовленный запрос защищает от инъекций, а password_hash создаёт безопасный хэш.

Типичная ошибка

Если не задать PDO::ATTR_ERRMODE, можно пропустить ошибки дублирования уникальных полей. Решение: обрабатывать исключение или проверять существование пользователя перед вставкой.


try {
    $stmt->execute([$username, $email, $hash]);
} catch (PDOException $e) {
    if ($e->getCode() == 23000) {
        // дубликат username или email
    }
}
    

Аутентификация


$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();

if ($user && password_verify($_POST['password'], $user['password_hash'])) {
    // успешный вход
    session_start();
    $_SESSION['user_id'] = $user['id'];
}
  

password_verify сравнивает введённый пароль с хэшем без раскрытия оригинала.

Проблема: слабый пароль

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


if (strlen($password) < 8) {
    // ошибка: пароль должен быть не менее 8 символов
}
    

Как сохранить пользователей в SQLite для небольшого проекта?

Цель

Использовать встроенную базу данных без отдельного сервера – идеально для прототипов, локальных тестов или приложений с одним пользователем.


$pdo = new PDO('sqlite:/path/to/users.db');
$pdo->exec('CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT UNIQUE NOT NULL,
    email TEXT UNIQUE NOT NULL,
    password_hash TEXT NOT NULL
)');
  

Код практически идентичен MySQL, меняется только DSN. Важно: SQLite не поддерживает параллельную запись на уровне таблиц, поэтому для высоконагруженных проектов не подходит.

Ошибка: блокировка базы

При одновременных записях SQLite может выдать ошибку database is locked. Решение: установить busy timeout.


$pdo->exec('PRAGMA busy_timeout = 3000');
    

Как обойтись без SQL, используя файловое хранилище (JSON)?

Цель

Хранить пользователей в JSON-файле для максимальной простоты, без установки СУБД. Подходит для одноразовых скриптов или демонстрации.


$users = json_decode(file_get_contents('users.json'), true);
$users[] = [
    'id' => uniqid(),
    'username' => $username,
    'email' => $email,
    'password_hash' => password_hash($password, PASSWORD_DEFAULT)
];
file_put_contents('users.json', json_encode($users, JSON_PRETTY_PRINT));
  

Проблема: гонка данных

При одновременных запросах файл может быть повреждён. Для реального проекта такая схема неприемлема.

Решение:

Использовать блокировку файла (flock) или перейти на реляционную базу.


$file = fopen('users.json', 'c+');
flock($file, LOCK_EX);
// чтение, запись
flock($file, LOCK_UN);
fclose($file);
    

Как применить ORM (например, Doctrine) для работы с пользователями?

Цель

Автоматизировать маппинг, миграции и запросы, ускорить разработку крупных проектов.


// Сущность
/**
 * @ORM\Entity
 * @ORM\Table(name="users")
 */
class User {
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;
    /** @ORM\Column(type="string", unique=true) */
    private $username;
    // ...
}

// Сохранение
$entityManager->persist($user);
$entityManager->flush();
  

Ошибка: настройка соединения

Если неверно указать драйвер или параметры, Doctrine не сможет подключиться. Решение: проверять конфигурацию через bin/console doctrine:database:create.

Расширенные примеры работы с базой пользователей

Пример 1. Обновление профиля с проверкой прав

Пример

if ($_SESSION['user_id'] != $user_id) {
    die('Доступ запрещён');
}

$stmt = $pdo->prepare('UPDATE users SET email = ? WHERE id = ?');
$stmt->execute([$new_email, $user_id]);
// При успехе возвращается количество затронутых строк (1)

Пример 2. Пагинация списка пользователей

Пример

$page = $_GET['page'] ?? 1;
$limit = 10;
$offset = ($page - 1) * $limit;

$countStmt = $pdo->query('SELECT COUNT(*) FROM users');
$total = $countStmt->fetchColumn();

$stmt = $pdo->prepare('SELECT id, username, email FROM users ORDER BY id DESC LIMIT ? OFFSET ?');
$stmt->bindValue(1, $limit, PDO::PARAM_INT);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll();
// $users - массив строк, $total - общее количество

Пример 3. Поиск пользователей по части имени с использованием LIKE

Пример

$searchTerm = '%' . $_GET['q'] . '%';
$stmt = $pdo->prepare('SELECT * FROM users WHERE username LIKE ?');
$stmt->execute([$searchTerm]);
$results = $stmt->fetchAll();
// Например, при q="jo" найдутся "john", "jo_doe"

Пример 4. Транзакция: массовая вставка или удаление с откатом

Пример

try {
    $pdo->beginTransaction();
    $pdo->exec('DELETE FROM users WHERE id = 1');
    $pdo->exec('DELETE FROM users WHERE id = 2');
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    echo 'Ошибка: ' . $e->getMessage();
}
// Если первый DELETE выполнился, а второй нет, изменения откатываются

Пример 5. Хеширование пароля с явным указанием алгоритма и стоимостью

Пример

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$stored = '$2y$12$...';
if (password_verify($password, $stored)) {
    echo 'Пароль верен';
}
// password_hash генерирует случайную соль, проверить можно только через password_verify

Пример 6. Запись ошибок в лог при неудачной аутентификации

Пример

if (!$user || !password_verify($password, $user['password_hash'])) {
    error_log('Неудачная попытка входа для пользователя: ' . $_POST['username']);
    // дополнительно: задержка перед ответом
    sleep(2);
    echo 'Неверный логин или пароль';
}
// Лог поможет выявить попытки подбора паролей

Пример 7. Использование подготовленного запроса с именованными параметрами

Пример

$stmt = $pdo->prepare('INSERT INTO users (username, email, password_hash) VALUES (:username, :email, :pass)');
$stmt->execute([
    ':username' => $username,
    ':email' => $email,
    ':pass' => $hash
]);
// Именованные параметры делают код более читаемым

База данных пользователей в PHP - comments

En
Php база данных пользователей (php)