Комментарии
Мы загрузили блог на веб-сервер и опубликовали несколько очень интересных постов с помощью Adminer. Люди читают наш блог и очень им довольны. Мы каждый день получаем много писем с похвалами. Но какая польза от всей этой похвалы, если она есть только в электронной почте и никто не может ее прочитать? Было бы лучше, если бы читатель мог комментировать статью напрямую, чтобы каждый мог прочитать, какие мы замечательные.
Итак, давайте запрограммируем комментарии.
Создание новой таблицы
Запустим Adminer и создадим таблицу comments
со следующими
столбцами:
id
int, отметим autoincrement (AI)post_id
, внешний ключ, который ссылается на таблицуposts
name
varchar, length 255email
varchar, length 255content
textcreated_at
timestamp
Таблица должна выглядеть примерно так:

Не забудьте снова использовать хранилище InnoDB.
CREATE TABLE `comments` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`post_id` int(11) NOT NULL,
`name` varchar(250) NOT NULL,
`email` varchar(250) NOT NULL,
`content` text NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB CHARSET=utf8;
Форма для комментирования
Сначала нам нужно создать форму, которая позволит пользователям комментировать посты. Nette Framework имеет отличную поддержку форм. Мы можем настроить их в презентере и отобразить в шаблоне.
Nette Framework использует концепцию компонентов. Компонент — это
повторно используемый класс или часть кода, который может быть
присоединен к другому компоненту. Даже презентер является
компонентом. Каждый компонент создается с помощью фабрики. Итак,
создадим фабрику для формы комментариев в презентере PostPresenter
.
protected function createComponentCommentForm(): Form
{
$form = new Form; // означает Nette\Application\UI\Form
$form->addText('name', 'Имя:')
->setRequired();
$form->addEmail('email', 'E-mail:');
$form->addTextArea('content', 'Комментарий:')
->setRequired();
$form->addSubmit('send', 'Опубликовать комментарий');
return $form;
}
Давайте снова немного объясним. Первая строка создает новый
экземпляр компонента Form
. Следующие методы добавляют HTML-инпуты в
определение этой формы. ->addText()
будет отображен как
<input type="text" name="name">
с <label>Имя:</label>
. Как вы,
вероятно, уже догадались, ->addTextArea()
будет отображен как
<textarea>
, а ->addSubmit()
как <input type="submit">
.
Существует гораздо больше подобных методов, но этих пока достаточно
для этой формы. Больше вы можете прочитаете в
документации.
Если форма уже определена в презентере, мы можем ее отрисовать
(отобразить) в шаблоне. Это делается путем размещения тега {control}
в конце шаблона, который отображает один конкретный пост, в
Post/show.latte
. Поскольку компонент называется commentForm
(название происходит от названия метода createComponentCommentForm
), тег
будет выглядеть так:
...
<h2>Добавьте новый комментарий</h2>
{control commentForm}
Теперь, если вы откроете страницу с деталями поста, в ее конце вы увидите новую форму для комментариев.
Сохранение в базу данных
Вы уже пробовали заполнить и отправить форму? Вероятно, вы заметили, что форма на самом деле ничего не делает. Нам нужно подключить метод обратного вызова (callback), который сохранит отправленные данные.
На строку перед return
в фабрике для компонента commentForm
поместим следующую строку:
$form->onSuccess[] = $this->commentFormSucceeded(...);
Предыдущая запись означает “после успешной отправки формы вызови
метод commentFormSucceeded
из текущего презентера”. Однако этот метод
еще не существует. Давайте его создадим:
private function commentFormSucceeded(\stdClass $data): void
{
$id = $this->getParameter('id');
$this->database->table('comments')->insert([
'post_id' => $id,
'name' => $data->name,
'email' => $data->email,
'content' => $data->content,
]);
$this->flashMessage('Спасибо за комментарий', 'success');
$this->redirect('this');
}
Этот метод разместим сразу после фабрики формы commentForm
.
Этот новый метод имеет один аргумент $data
, который является
объектом, содержащим отправленные данные формы. Мы получаем ID поста из
параметров запроса. А затем сохраняем данные в таблицу базы данных
comments
.
Есть еще два метода, которые заслуживают объяснения. Метод
redirect('this')
буквально перенаправляет обратно на текущую страницу.
Это целесообразно делать после каждой отправки формы, если она
содержала валидные данные и обратный вызов выполнил операцию так, как
должен был. А также, если мы перенаправляем страницу после отправки
формы, мы не увидим хорошо известное сообщение
Хотите отправить данные формы снова?
, которое иногда можно
увидеть в браузере. (В общем, после отправки формы методом POST
всегда должно следовать перенаправление на GET
действие.)
Метод flashMessage()
предназначен для информирования пользователя о
результате какой-либо операции. Поскольку мы перенаправляем,
сообщение не может быть просто передано шаблону и отрисовано. Поэтому
существует этот метод, который сохраняет это сообщение и делает его
доступным при следующей загрузке страницы. Flash-сообщения
отрисовываются в главном шаблоне app/Presentation/@layout.latte
и
выглядят так:
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
{$flash->message}
</div>
Как мы уже знаем, flash-сообщения автоматически передаются в шаблон, так что нам не нужно об этом много думать, это просто работает. Для получения дополнительной информации посетите документацию.
Отображение комментариев
Это одна из тех вещей, которые вы просто полюбите. Nette Database имеет отличную функцию под названием Explorer. Помните ли вы еще, что таблицы в базе данных мы намеренно создавали с помощью хранилища InnoDB? Adminer таким образом создал то, что называется внешние ключи, которые сэкономят нам много работы.
Nette Database Explorer использует внешние ключи для разрешения взаимных связей между таблицами и на основе знаний об этих связях умеет автоматически создавать запросы к базе данных.
Как вы наверняка помните, в шаблон мы передали переменную $post
с
помощью метода PostPresenter::renderShow()
, и теперь мы хотим итерировать по
всем комментариям, которые имеют значение столбца post_id
,
совпадающее с $post->id
. Этого можно достичь вызовом
$post->related('comments')
. Да, вот так просто. Посмотрим на
итоговый код:
public function renderShow(int $id): void
{
...
$this->template->post = $post;
$this->template->comments = $post->related('comments')->order('created_at');
}
И шаблон:
...
<h2>Комментарии</h2>
<div class="comments">
{foreach $comments as $comment}
<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
{$comment->name}
</a></b> написал:</p>
<div>{$comment->content}</div>
{/foreach}
</div>
...
Обратите внимание на специальный атрибут n:tag-if
. Вы уже знаете,
как работают n:атрибуты
. Если к атрибуту добавить префикс
tag-
, функциональность применяется только к HTML-тегу, а не к его
содержимому. Это позволяет нам сделать имя комментатора ссылкой
только в том случае, если он предоставил свой e-mail. Эти две строки
идентичны:
<strong n:tag-if="$important"> Добрый день! </strong>
{if $important}<strong>{/if} Добрый день! {if $important}</strong>{/if}