Вставка с помощью Active Record
Привет! В предыдущем уроке мы написали метод 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);
}
Остаётся выполнить запрос, подставив нужные параметры.
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();
}
Снова перезагрузим страницу.
Как видим, теперь у объекта после вызова метода save() появился id.
Однако, в свойстве createdAt по-прежнему null. Доработать этот недостаток вы сможете в домашнем задании. А на этом данный урок заканчивается.
Текущая версия проекта на гитхабе.
Комментарии