JSON-файл помог пережить перезапуск, но искать записи и обновлять отдельные поля в нём неудобно. Добавим SQLite — файловую базу данных, которой можно пользоваться локально без отдельного сервера. Доступ к ней сделаем через PDO, общий интерфейс PHP для работы с базами данных.
Проверьте драйвер и создайте таблицу
Для подключения нужны PDO и драйвер PDO_SQLITE. Если конструктор PDO сообщает, что драйвер не найден, включите соответствующее расширение в используемой установке PHP и проверьте список расширений командой php -m.
Подключите PDO-драйвер SQLite и один раз выполните миграцию:
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author TEXT NOT NULL,
year INTEGER NOT NULL,
status TEXT NOT NULL
);
NOT NULL запрещает отсутствие важных значений, а первичный ключ id отличает записи. Схему создают миграцией при подготовке проекта, а не на каждом запросе.
Открываем SQLite через PDO
<?php
declare(strict_types=1);
$databasePath = dirname(__DIR__) . '/storage/reading-log.sqlite';
$pdo = new PDO('sqlite:' . $databasePath, options: [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
Путь находится вне публичной папки. Режим исключений помогает обнаружить сбой соединения или запроса. Хранилище получает путь из конфигурации, а не из URL.
Подготавливаем запрос на добавление
$statement = $pdo->prepare(
'INSERT INTO books (title, author, year, status)
VALUES (:title, :author, :year, :status)'
);
$statement->execute([
'title' => $book->title(),
'author' => $book->author(),
'year' => $book->year(),
'status' => $book->status()->value,
]);
Плейсхолдеры отделяют SQL-команду от значений. Не собирайте запрос конкатенацией с пользовательским вводом: подготовленные запросы не дают значениям стать частью SQL-кода.
Читаем список
$statement = $pdo->query(
'SELECT id, title, author, year, status
FROM books
ORDER BY year DESC'
);
$rows = $statement->fetchAll();
Здесь SQL статический, поэтому подходит query(). Для пользовательского фильтра применяйте подготовленный запрос:
$statement = $pdo->prepare(
'SELECT id, title, author, year, status FROM books WHERE status = :status'
);
$statement->execute(['status' => ReadingStatus::Read->value]);
$rows = $statement->fetchAll();
Имя столбца не является значением
Плейсхолдер подходит для года или статуса, но не для имени таблицы, столбца и направления сортировки. Если посетитель выбирает сортировку, сопоставьте его выбор со списком заранее заданных SQL-фрагментов — не вставляйте параметр напрямую.
SQLite или серверная база
SQLite подходит для небольшого проекта и локальной разработки. Если приложение работает на нескольких серверах, активно меняет данные одновременно или требует расширенного управления доступом, подберите серверную базу и настройте резервные копии. PDO упрощает интерфейс, но не делает базы данных одинаковыми.
Передайте операции отдельному репозиторию: контроллеру не нужно знать SQL. В следующей части добавим тесты, чтобы после замены хранилища поведение не изменилось незаметно.
Навигация по курсу
Часть 14 из 16.
← Часть 13: PHP-формы POST: проверка данных и защита CSRF · Часть 15: Тестирование PHP: первые тесты PHPUnit →
0
10