Работать напрямую с PDO в коде не очень удобно из-за большого количества низкоуровневых деталей и повторяющегося шаблонного кода. Кроме того, нам постоянно приходится преобразовывать данные в одну и в другую сторону.
Чтобы решить эту проблему, можно скрыть работу с базой за какой-то абстракцией. Один из вариантов такой изоляции называют DAO (Data Access Object).
Концепция DAO очень проста. Она сводится к созданию класса под каждую таблицу в базе данных. В классе реализуются методы, которые сохраняют, удаляют или ищут сущности в этой таблице. Когда речь идет о пользователях, наш класс DAO может выглядеть так:
class UserDAO {
    private \PDO $pdo;
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    public function save(User $user): void
    {
        // Если пользователь новый, выполняем вставку
        // Иначе обновляем
        if (is_null($user->getId())) {
            $sql = "INSERT INTO users (username, phone) VALUES (?, ?)";
            $stmt = $this->pdo->prepare($sql);
            $username = $user->getUsername();
            $phone = $user->getPhone();
            $stmt->bindParam(1, $username);
            $stmt->bindParam(2, $phone);
            $stmt->execute();
            // Извлекаем идентификатор и добавляем в сохраненный объект
            $id = (int) $this->pdo->lastInsertId();
            $user->setId($id);
        } else {
            // Здесь код обновления существующей записи
        }
    }
    public function find(int $id): ?User
    {
        $sql = "SELECT * FROM users WHERE id = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$id]);
        $result = $stmt->fetch();
        if ($result) {
            $user = new User($result['username'], $result['phone']);
            $user->setId($id);
            return $user;
        }
        return null;
    }
}
Метод save() в этом классе не только сохраняет данные в базу, но и изменяет переданный объект, устанавливая внутри него идентификатор. Зачем это нужно? Код приложения работает с объектом, а не с базой напрямую. Соответственно, любые изменения в базе должны отражаться на объекте.
Если бы мы не установили идентификатор после сохранения пользователя, то не смогли бы:
- Формировать ссылки — например, ссылку на редактирование пользователя
 - Сравнивать объекты друг с другом
 - Отличать существующих пользователей от новых, которых мы еще не сохранили в базу данных
 - Обеспечить работу кода так, чтобы полноценная версия 
save()проверяла наличие идентификатора и выполняла обновление данных, если его нет 
Рассмотрим несколько примеров использования DAO:
// Устанавливаем соединение с базой данных
$conn = new PDO('sqlite::memory:');
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$dao = new UserDAO($conn);
$user1 = new User('John', '555-1234');
$user1->getId(); // null
$dao->save($user);
$user1->getId(); // Здесь уже выводится какой-то id
$user2 = $dao->find($user1->getId());
$user2->getId() == $user1->getId();
Самостоятельная работа
- Выполните шаги из урока на своем компьютере
 - Перепишите код нашего приложения, используя DAO для таблицы пользователей. Чтобы это сделать, создайте класс 
User, который будет представлять пользователя - Добавьте в DAO еще один метод, который сможет удалять пользователей из таблицы по их идентификатору
 
Задача со звездочкой. База данных в памяти хороша для обучения и тестирования, но для рабочего приложения не годится. Заменим SQLite в памяти на полнофункциональную базу данных. Одна из наиболее распространенных СУБД — PostgreSQL. Для подключения к ней вам потребуется установить соответствующий драйвер и поднять локально PostgreSQL. Сделать это можно по нашей инструкции
- Подключите созданный DAO к PostgreSQL. Попробуйте добавлять и удалять пользователей при помощи DAO, убедитесь, что все работает корректно
 
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.