Коментарі

Блог був розгорнутий, ми написали кілька дуже хороших постів для блогу і опублікували їх через Adminer. Люди читають блог, і вони дуже захоплені нашими ідеями. Щодня ми отримуємо безліч листів із похвалами. Але до чого всі ці похвали, якщо ми отримуємо їх тільки електронною поштою, і ніхто більше не може їх прочитати? Хіба не краще було б, якби люди могли залишати коментарі прямо в блозі, щоб усі інші могли прочитати, які ми чудові?

Давайте зробимо всі статті коментованими.

Створення нової таблиці

Знову запустіть Adminer і створіть нову таблицю з іменем comments з цими стовпцями:

  • id int, позначте автоінкремент (AI)
  • post_id, зовнішній ключ, що посилається на таблицю posts.
  • name varchar, довжина 255
  • email varchar, довжина 255
  • content text
  • created_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 має чудову підтримку форм. Вони можуть бути налаштовані в презентері та відображені в шаблоні.

У Nette є поняття компоненти. Компонент – це багаторазово використовуваний клас або фрагмент коду, який може бути приєднаний до іншого компонента. Навіть презентер є компонентом. Кожен компонент створюється за допомогою фабрики компонентів. Отже, давайте визначимо фабрику форми коментарів у PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // означає Nette\Application\UI\Form

	$form->addText('name', 'Ваше ім'я:')
		->setRequired();

	$form->addEmail('email', 'Імейл:');

	$form->addTextArea('content', 'Коментар:')
		->setRequired();

	$form->addSubmit('send', 'Відправити');

	return $form;
}

Давайте трохи пояснимо це. Перший рядок створює новий екземпляр компонента Form. Методи, які вказані нижче, розміщують HTML-елементи input всередині форми. ->addText буде відображатися як <input type=text name=name>, с <label>Ваше имя:</label>. Як ви вже здогадалися, ->addTextArea прикріплює <textarea>, а ->addSubmit додає <input type=submit>. Подібних методів більше, але це все, що вам потрібно знати просто зараз. Ви можете дізнатися більше в документації.

Після того як компонент форми визначено в презентері, ми можемо відобразити його в шаблоні. Для цього помістіть тег {control} в кінець шаблону деталізації поста в app/UI/Post/show.latte. Оскільки ім'я компонента – commentForm (воно походить від назви методу createComponentCommentForm), тег матиме такий вигляд:

...
<h2>Оставить комментарий</h2>

{control commentForm}

Тепер, якщо ви перейдете на окрему сторінку якогось поста, там буде нова форма для розміщення коментарів.

Збереження в базі даних

Чи намагалися ви надіслати будь-які дані? Ви могли помітити, що форма не виконує жодних дій. Вона просто є, виглядає круто і нічого не робить. Ми повинні прикріпити до неї метод зворотного виклику, який буде зберігати передані дані.

Помістіть наступний рядок перед рядком return у фабриці компонентів для commentForm:

$form->onSuccess[] = $this->commentFormSucceeded(...);

Це означає “після успішного надсилання форми викликати метод commentFormSucceeded поточного презентера”. Цього методу ще не існує, тому давайте створимо його.

private function commentFormSucceeded(\stdClass $data): void
{
	$postId = $this->getParameter('postId');

	$this->database->table('comments')->insert([
		'post_id' => $postId,
		'name' => $data->name,
		'email' => $data->email,
		'content' => $data->content,
	]);

	$this->flashMessage('Спасибо за комментарий!', 'success');
	$this->redirect('this');
}

Ви повинні розмістити його відразу після фабрики компонента commentForm.

Новий метод має один аргумент, яким є екземпляр форми, що відправляється, створений фабрикою компонентів. Ми отримуємо передані значення в $data. Потім ми вставляємо дані в таблицю бази даних comments.

Необхідно пояснити ще два виклики методу. $this->redirect('this') буквально перенаправляє на поточну сторінку. Ви повинні робити це кожного разу, коли форма відправлена, дійсна, і операція зворотного виклику зробила те, що повинна була зробити. Крім того, коли ви перенаправляєте сторінку після надсилання форми, ви не побачите добре відомого повідомлення Вы хотите отправить данные сообщения снова?, яке іноді можна побачити в браузері. (Загалом, після надсилання форми методом POST, ви завжди повинні перенаправляти користувача на дію GET).

$this->flashMessage призначений для інформування користувача про результат деякої операції. Оскільки ми перенаправляємо, повідомлення не може бути безпосередньо передано в шаблон і відображено. Тому існує метод, який збереже його і зробить доступним при наступному завантаженні сторінки. Флеш-повідомлення відображаються в стандартному файлі app/UI/@layout.latte, і виглядають вони так:

<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
	{$flash->message}
</div>

Як ми вже знаємо, вони автоматично передаються в шаблон, тому вам не потрібно багато думати про це, це просто працює. Для отримання більш детальної інформації ознайомтеся з документацією.

Відображення коментарів

Це одна з тих речей, які вам просто сподобаються. Nette Database має класну функцію під назвою Explorer. Чи пам'ятаєте ви, що ми створили таблиці як InnoDB? Adminer створив так звані зовнішні ключі, які заощадять нам тонну роботи.

Nette Database Explorer використовує зовнішні ключі для розв'язання відносин між таблицями, а знаючи ці відносини, він може автоматично створювати запити для вас.

Як ви пам'ятаєте, ми передали змінну $post шаблону в PostPresenter::renderShow() і тепер хочемо перебрати всі коментарі, у яких стовпець post_id дорівнює нашому $post->id. Ви можете зробити це, викликавши $post->related('comments'). Це так просто. Подивіться на отриманий код.

public function renderShow(int $postId): 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-, він обводитиме тільки теги, а не їхній вміст. Це дає змогу перетворити ім'я коментатора на посилання, якщо він вказав свою електронну пошту. Ці два рядки ідентичні за результатами:

<strong n:tag-if="$important"> Здравствуйте! </strong>

{if $important}<strong>{/if} Здравствуйте! {if $important}</strong>{/if}