Комментарии

Комментарии

Блог был развернут, мы написали очень хорошие записи и опубликовали их через 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 Framework имеет огромную поддержку форм. Их можно настроить в Презентере и вывести в шаблоне.

Nette Framework имеет концепцию компонент. Компонент представляет собой многократно используемый класс или фрагмент кода, который может быть присоединен к другому компоненту. Даже презентер является компонентом. Каждый компонент создается с использованием фабрики компонентов. Итак, давайте определим фабрику комментариев в PostPresenter.

    protected function createComponentCommentForm()
    {
        $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 в определение формы.
->addText будет отображаться как <input type=text name=name>, с <label>Ваше имя:</label>. Как вы, наверное, уже догадались, ->addTextArea добавляет <textarea> и ->addSubmit добавляет <input type=submit>.
Таких методов больше, но это все, что вам нужно знать сейчас. Вы можете узнать больше в документации.

Когда компонент формы определен в презентере, мы можем отобразить его в шаблоне. Для этого разместите макрос {control} в конце шаблона записи в app/presenters/templates/Post/show.latte. Поскольку имя компонента commentForm (оно получено из имени метода createComponentCommentForm), макрос будет выглядеть следующим образом

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

{control commentForm}
...

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

Сохранение в базу данных

Вы пытались отправить некоторые данные? Вы заметили, что форма не выполняет никаких действий. Она просто есть на странице, выглядит круто и ничего не делает. Мы должны добавить к ней метод обратного вызова, который сохранит отправленные данные.

Добавьте следующую строку перед return в фабрике компонента commentForm:

$form->onSuccess[] = [$this, 'commentFormSucceeded'];

Это означает «как форма будет отправлена, вызвать метод commentFormSucceeded у текущего презентера». Этого метода еще не существует, поэтому давайте создадим его.

    public function commentFormSucceeded($form, $values)
    {
        $postId = $this->getParameter('postId');

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

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

Вы должны разместить его после фабрики компонента commentForm.

Новый метод имеет один аргумент, который представляет собой экземпляр отправленной формы, созданый фабрикой компонента. Отправленные данные мы получаем в $values. И затем вставляем в базу данных, в таблицу comments.

Объясним еще два метода. redirect буквально перенаправляет на текущую страницу. Вы должны делать это каждый раз когда форма отправлена, валидна, и в функции обратного вызова было выполнено то, что должно было быть выполнено. Кроме того, когда вы перенаправляете страницу после отправки формы, вы не увидите хорошо известного сообщения Хотите ли вы отправить данные сообщения снова?, которое иногда можно увидеть в браузере. (В общем, после отправки формы методом POST вы всегда должны перенаправлять пользователя на действие GET.)

flashMessage предназначен для информирования пользователя о результате какой-либо операции. Поскольку мы перенаправляем, сообщение нельзя напрямую передать шаблону и визуализировать. Для этого и существует этот метод, который сохранит сообщение и сделает доступным при загрузке следующей страницы. Флэш-сообщения отображаются в файле app/presenters/templates/@layout.latte, и это выглядит так

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

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

Вывод комментариев

Это одна из вещей, которую вы просто полюбите. У Nette\Database есть крутая особенность под названием Selection API. Вы помните, что мы создали таблицы как InnoDB? Adminer создал так называемые внешние ключи, которые спасут нас от массы работы.

Nette\Database использует внешние ключи для связи между таблицами и зная связи, он может автоматически создавать запросы.

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

    public function renderShow($postId)
    {
        ...
        $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> said:</p>
 <div>{$comment->content}</div>
 {/foreach}
 </div>

Обратите внимание на специальный атрибут n:tag-if. Вы уже знаете, как работают n: attributes. Ну, если вы добавите атрибут с tag-, он будет обтекать только теги, а не их содержимое. Это позволяет сделать имя комментатора ссылкой, если он оставил адрес электронной почты. Эти две строки идентичны в результатах:

<strong n:tag-if="$important"> Привет! </strong>
{if $important}<strong>{/if} Привет! {if $important}</strong>{/if}
Яндекс.Метрика