Вставка с помощью Active Record

29.07.2023 в 18:05
8785
+519

Привет! В предыдущем уроке мы написали метод save(), который в зависимости от того, есть ли у объекта id, решает – обновить запись или создать новую. Он вызывает в свою очередь другие методы:

  • update(), если id у объекта есть;
  • insert(), если это свойство у объекта равно null.

В прошлом уроке мы реализовали метод update(), который позволяет обновлять уже существующие записи в базе. В этом уроке мы напишем метод insert(), который будет создавать новые записи в нашей базе данных.

Давайте добавим новый роут, с помощью которого будут создаваться новые статьи.

src/routes.php

<?php

return [
    ...
    '~^articles/add$~' => [\MyProject\Controllers\ArticlesController::class, 'add'],
    ... 
];

Добавим новый экшн в контроллере (не забудьте прописать неймспейс для модели User):

src/MyProject/Controllers/ArticlesController.php

public function add(): void
{
    $author = User::getById(1);

    $article = new Article();
    $article->setAuthor($author);
    $article->setName('Новое название статьи');
    $article->setText('Новый текст статьи');

    $article->save();

    var_dump($article);
}

И добавим сеттер автора в сущности Article:

src/MyProject/Models/Articles/Article.php

/**
 * @param User $author
 */
public function setAuthor(User $author): void
{
    $this->authorId = $author->getId();
}

Если сейчас мы перейдём по новому адресу:
http://myproject.loc/articles/add
То увидим созданный нами объект.

Добавление статьи

Разумеется, в базе данных он сейчас не появится, так как метод insert() в классе ActiveRecordEntity сейчас пуст.
Отсутствие записи в БД

Самое время его написать :)

Реализация метода insert()

Давайте рассмотрим синтаксис SQL-запроса, который нам нужно написать:

INSERT INTO <имя таблицы> (<имя столбца 1>, <имя столбца 2>,...) VALUES (<значение столбца 1>, <значение столбца 2>,…);

Для сущности Article запрос на вставку будет выглядеть вот так:

INSERT INTO `articles` (`author_id`, `name`, `text`) VALUES (:author_id, :name, :text)

А затем нам нужно будет передать массив с параметрами, вроде такого:

[':author_id' => 1, ':name' => 'Название', ':text' => 'Текст']

Первым делом давайте посмотрим на то, какие данные вообще у нас есть:

src/MyProject/Models/ActiveRecordEntity.php

private function insert(array $mappedProperties): void
{
    var_dump($mappedProperties);
}

Подготовленные поля для запроса

Мы видим, что у нас есть 2 поля со значением null. Это поля id и created_at. Поле id нам в запросе не нужно, так как для него будет автоматически выдано значение на уровне базы данных, так как оно типа AUTOINCREMENT, а для поля created_at задано значение по умолчанию – CURRENT_TIMESTAMP. Таким образом, эти поля можно вообще убрать из запроса. Для этого мы отфильтруем элементы в массиве от тех, значение которых = NULL:

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);
    var_dump($filteredProperties);
}

Отфильтрованные значения

Отлично, теперь у нас осталось всего 3 поля. Давайте для начала сформируем массив, содержащий названия столбцов в таблице:

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
    }

    var_dump($columns);
}

Названия колонок в БД

А теперь подготовим массив с именами подстановок, вроде :author_id и :name.

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    $paramsNames = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
        $paramName = ':' . $columnName;
        $paramsNames[] = $paramName;
    }

    var_dump($columns);
    var_dump($paramsNames);
}

Имена подстановок

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

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    $paramsNames = [];
    $params2values = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
        $paramName = ':' . $columnName;
        $paramsNames[] = $paramName;
        $params2values[$paramName] = $value;
    }

    var_dump($columns);
    var_dump($paramsNames);
    var_dump($params2values);
}

Подстановки и значения

Все части для запроса готовы. Остаётся лишь собрать готовый запрос.

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    $paramsNames = [];
    $params2values = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
        $paramName = ':' . $columnName;
        $paramsNames[] = $paramName;
        $params2values[$paramName] = $value;
    }

    $columnsViaSemicolon = implode(', ', $columns);
    $paramsNamesViaSemicolon = implode(', ', $paramsNames);

    $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ') VALUES (' . $paramsNamesViaSemicolon . ');';

    var_dump($sql);
}

Сформированный SQL-запрос

Остаётся выполнить запрос, подставив нужные параметры.

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    $paramsNames = [];
    $params2values = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
        $paramName = ':' . $columnName;
        $paramsNames[] = $paramName;
        $params2values[$paramName] = $value;
    }

    $columnsViaSemicolon = implode(', ', $columns);
    $paramsNamesViaSemicolon = implode(', ', $paramsNames);

    $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ') VALUES (' . $paramsNamesViaSemicolon . ');';

    $db = Db::getInstance();
    $db->query($sql, $params2values, static::class);
}

Если мы сейчас обновим нашу страничку, то увидим, что в базе данных успешно добавилась новая запись.

Добавленная запись

Объект после создания записи в БД

Обратите внимание, что в контроллере мы вывели наш объект уже после сохранения в базу:

src/MyProject/Controllers/ArticlesController.php

public function add(): void
{
    $author = User::getById(1);

    $article = new Article();
    $article->setAuthor($author);
    $article->setName('Новое название статьи');
    $article->setText('Новый текст статьи');

    $article->save();

    var_dump($article);
}

Однако, у него не обновилось поле id, а ведь это нам необходимо. Потому что если мы что-либо изменим сейчас в этом объекте и снова вызовем метод save(), то вместо обновления записи в базе, будет создана ещё одна. Так произойдет, потому что в методе save() мы проверяем значение поля id, и если оно равно null, то мы вызываем insert(), а не update(). Давайте исправим это недоразумение. Для того, чтобы получить id последней вставленной записи в базе (в рамках текущей сессии работы с БД) можно использовать метод lastInsertId() у объекта PDO. Давайте в нашем классе Db добавим следующий метод:

src/MyProject/Services/Db.php

public function getLastInsertId(): int
{
    return (int) $this->pdo->lastInsertId();
}

Теперь используем его в методе insert():

private function insert(array $mappedProperties): void
{
    $filteredProperties = array_filter($mappedProperties);

    $columns = [];
    $paramsNames = [];
    $params2values = [];
    foreach ($filteredProperties as $columnName => $value) {
        $columns[] = '`' . $columnName. '`';
        $paramName = ':' . $columnName;
        $paramsNames[] = $paramName;
        $params2values[$paramName] = $value;
    }

    $columnsViaSemicolon = implode(', ', $columns);
    $paramsNamesViaSemicolon = implode(', ', $paramsNames);

    $sql = 'INSERT INTO ' . static::getTableName() . ' (' . $columnsViaSemicolon . ') VALUES (' . $paramsNamesViaSemicolon . ');';

    $db = Db::getInstance();
    $db->query($sql, $params2values, static::class);
    $this->id = $db->getLastInsertId();
}

Снова перезагрузим страницу.

Объект с полем id

Как видим, теперь у объекта после вызова метода save() появился id.
Однако, в свойстве createdAt по-прежнему null. Доработать этот недостаток вы сможете в домашнем задании. А на этом данный урок заканчивается.

Текущая версия проекта на гитхабе.

loader
29.07.2023 в 18:05
8785
+519
Домашнее задание

Доработайте метод insert() таким образом, чтобы поля объекта обновлялись значениями из БД. Например, в поле createdAt должна появиться строка с датой.

Комментарии
Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Логические задачи с собеседований