Komentarze
Blog został wdrożony, napisaliśmy kilka bardzo ciekawych wpisów blogowych i opublikowaliśmy je przez Adminer. Ludzie czytają naszego bloga i są nim bardzo zachwyceni. Codziennie dostajemy mnóstwo e-maili z pochwałami. Ale co nam po tych wszystkich pochwałach, jeśli są tylko w naszej skrzynce i nikt inny nie może ich przeczytać? Czy nie byłoby lepiej, gdyby czytelnicy mogli komentować bezpośrednio pod artykułami, żeby wszyscy widzieli, jacy jesteśmy niesamowici?
Zrealizujmy komentarze.
Utworzenie nowej tabeli
Odpalmy znowu Adminer i utwórzmy tabelę comments z tymi kolumnami:
idint, zaznacz autoincrement (AI)post_id, klucz obcy odwołujący się do tabelipostsnamevarchar, długość 255emailvarchar, długość 255contenttextcreated_attimestamp
Tabela powinna wyglądać mniej więcej tak:

Pamiętaj, żeby znowu użyć silnika składowania InnoDB.
CREATE TABLE `comments` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`post_id` int NOT NULL,
`name` varchar(255) NOT NULL,
`email` varchar(255) 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;
Formularz do komentowania
Najpierw musimy utworzyć formularz pozwalający użytkownikom komentować wpisy. Nette Framework ma doskonałe wsparcie dla formularzy. Możemy je skonfigurować w presenterze i wyrenderować w szablonie.
Nette Framework używa koncepcji komponentów. Komponent to klasa albo kawałek kodu nadający się do ponownego
użycia, który można podpiąć do innego komponentu. Nawet presenter jest komponentem. Każdy komponent tworzy się metodą
fabrykującą. Utwórzmy w PostPresenter fabrykę formularza komentarzy.
protected function createComponentCommentForm(): Form
{
$form = new Form; // czyli Nette\Application\UI\Form
$form->addText('name', 'Twoje imię:')
->setRequired();
$form->addEmail('email', 'Email:');
$form->addTextArea('content', 'Komentarz:')
->setRequired();
$form->addSubmit('send', 'Opublikuj komentarz');
return $form;
}
Wyjaśnijmy to pokrótce. Pierwsza linia tworzy nową instancję komponentu Form. Kolejne metody podpinają do
definicji formularza inputy HTML. ->addText renderuje się jako <input type="text" name="name">
z <label>Twoje imię:</label>. Jak się domyślasz, ->addTextArea podpina
<textarea>, a ->addSubmit dodaje <input type="submit">. Podobnych metod jest
wiele, ale na razie te wystarczą. Więcej dowiesz się w dokumentacji.
Gdy komponent formularza jest już zdefiniowany w presenterze, możemy go wyrenderować (wyświetlić) w szablonie. Żeby to
zrobić, umieść tag {control} na końcu szablonu wyświetlającego pojedynczy wpis, w Post/show.latte.
Ponieważ komponent nazywa się commentForm (wywodzi się to od nazwy metody
createComponentCommentForm), tag będzie wyglądać tak:
...
<h2>Dodaj nowy komentarz</h2>
{control commentForm}
Teraz, jeśli wyświetlisz stronę szczegółów wpisu, zobaczysz na końcu nowy formularz komentarzy.
Zapis do bazy danych
Próbowałeś wypełnić i wysłać formularz? Być może zauważyłeś, że formularz właściwie jeszcze nic nie robi. Musimy podpiąć metodę callback, która zapisze wysłane dane.
Dodaj przed instrukcją return w metodzie fabrykującej komponent commentForm poniższą linię:
$form->onSuccess[] = $this->commentFormSucceeded(...);
Linia ta oznacza “po udanym wysłaniu formularza wywołaj metodę commentFormSucceeded bieżącego
presentera”. Metoda ta jeszcze nie istnieje, więc ją utwórzmy:
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('Dziękujemy za komentarz', 'success');
$this->redirect('this');
}
Powinieneś umieścić ją zaraz za fabryką komponentu commentForm.
Nowa metoda ma jeden argument, którym jest instancja wysyłanego formularza utworzona przez fabrykę komponentu. Wysłane
wartości otrzymujemy w $data. A potem wstawiamy dane do tabeli comments w bazie danych.
Do wyjaśnienia zostały jeszcze dwa wywołania metod. Metoda redirect('this') dosłownie przekierowuje
z powrotem na bieżącą stronę. Powinieneś robić to za każdym razem, gdy formularz zostanie pomyślnie wysłany i operacja
callbacku się zakończy. Przekierowanie po wysłaniu formularza zapobiega też znanemu komunikatowi przeglądarki “Czy chcesz
ponownie wysłać dane?”. (Ogólnie po wysłaniu formularza metodą POST powinieneś zawsze przekierować na akcję
GET.)
Metoda flashMessage służy do poinformowania użytkownika o wyniku operacji. Ponieważ przekierowujemy,
komunikatu nie da się po prostu przekazać do szablonu i wyrenderować bezpośrednio. Metoda ta zapisuje komunikat
i udostępnia go przy kolejnym wczytaniu strony. Wiadomości flash renderowane są w głównym szablonie layoutu
app/Presentation/@layout.latte i wyglądają tak:
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
{$flash->message}
</div>
Jak już wiemy, wiadomości flash przekazywane są do szablonu automatycznie, więc nie musimy się nad tym zbytnio zastanawiać, po prostu działa. Więcej szczegółów znajdziesz w dokumentacji.
Renderowanie komentarzy
To jedna z tych funkcji, które po prostu pokochasz. Nette Database ma świetną funkcję zwaną Explorer. Pamiętasz, jak celowo utworzyliśmy tabele bazy danych z silnikiem InnoDB? Adminer utworzył tak zwane klucze obce, które oszczędzają nam mnóstwo pracy.
Nette Database Explorer wykorzystuje te klucze obce do rozwiązywania relacji między tabelami. Znając te relacje, potrafi automatycznie tworzyć za Ciebie zapytania do bazy danych.
Jak zapewne pamiętasz, w PostPresenter::renderShow() przekazaliśmy do szablonu zmienną $post.
Teraz chcemy przejść wszystkie komentarze, w których kolumna post_id odpowiada naszemu $post->id.
Osiągniemy to, wywołując $post->related('comments'). Tak, to takie proste. Spójrzmy na ostateczny kod:
public function renderShow(int $id): void
{
// ...
$this->template->post = $post;
$this->template->comments = $post->related('comments')->order('created_at');
}
I szablon:
...
<h2>Komentarze</h2>
<div class="comments">
{foreach $comments as $comment}
<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
{$comment->name}
</a></b> napisał:</p>
<div>{$comment->content}</div>
{/foreach}
</div>
...
Zwróć uwagę na specjalny atrybut n:tag-if. Wiesz już, jak działają n:atrybuty. Jeśli
poprzedzisz atrybut przedrostkiem tag-, funkcjonalność zadziała tylko na sam tag HTML, a nie na jego zawartość.
Pozwala nam to zrobić z imienia komentującego odnośnik tylko wtedy, gdy podał swój e-mail. Te dwie linie dają
identyczny wynik:
<strong n:tag-if="$important"> Hello there! </strong>
{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if}