Komentarze
Umieściliśmy bloga na serwerze internetowym i opublikowaliśmy kilka bardzo interesujących postów za pomocą Adminera. Ludzie czytają naszego bloga i są nim bardzo podekscytowani. Codziennie otrzymujemy wiele e-maili z pochwałami. Ale po co nam te wszystkie pochwały, skoro mamy je tylko w e-mailu i nikt nie może ich przeczytać? Byłoby lepiej, gdyby czytelnik mógł bezpośrednio komentować artykuł, aby każdy mógł przeczytać, jacy jesteśmy wspaniali.
Zaprogramujmy więc komentarze.
Tworzenie nowej tabeli
Uruchomimy Adminera i utworzymy tabelę comments
z następującymi kolumnami:
id
int, zaznaczamy autoincrement (AI)post_id
, klucz obcy, który odwołuje się do tabeliposts
name
varchar, length 255email
varchar, length 255content
textcreated_at
timestamp
Tabela powinna więc wyglądać mniej więcej tak:

Pamiętaj, aby ponownie użyć silnika 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;
Formularz do komentowania
Najpierw musimy stworzyć formularz, który umożliwi użytkownikom komentowanie postów. Nette Framework ma niesamowite wsparcie dla formularzy. Możemy je skonfigurować w presenterze i wyrenderować w szablonie.
Nette Framework wykorzystuje koncept komponentów. Komponent to reużywalna klasa lub część kodu, która może
być dołączona do innego komponentu. Nawet presenter jest komponentem. Każdy komponent jest tworzony za pomocą fabryki.
Stworzymy więc fabrykę dla formularza komentarzy w presenterze PostPresenter
.
protected function createComponentCommentForm(): Form
{
$form = new Form; // oznacza Nette\Application\UI\Form
$form->addText('name', 'Imię:')
->setRequired();
$form->addEmail('email', 'E-mail:');
$form->addTextArea('content', 'Komentarz:')
->setRequired();
$form->addSubmit('send', 'Opublikuj komentarz');
return $form;
}
Wyjaśnijmy to sobie jeszcze raz. Pierwsza linia tworzy nową instancję komponentu Form
. Następne metody
dołączają inputy HTML do definicji tego formularza. ->addText()
zostanie wyrenderowane jako
<input type="text" name="name">
z <label>Imię:</label>
. Jak już pewnie słusznie
się domyślacie, ->addTextArea()
zostanie wyrenderowane jako <textarea>
, a
->addSubmit()
jako <input type="submit">
. Istnieje znacznie więcej podobnych metod, ale te na
razie wystarczą dla tego formularza. Więcej przeczytasz w dokumentacji.
Jeśli formularz jest już zdefiniowany w presenterze, możemy go wyrenderować (wyświetlić) w szablonie. Zrobimy to,
umieszczając znacznik {control}
na końcu szablonu, który renderuje jeden konkretny post, w
Post/show.latte
. Ponieważ komponent nazywa się commentForm
(nazwa pochodzi od nazwy metody
createComponentCommentForm
), znacznik będzie wyglądał tak:
...
<h2>Dodaj nowy komentarz</h2>
{control commentForm}
Gdy teraz wyświetlisz stronę ze szczegółami posta, na jego końcu zobaczysz nowy formularz do komentowania.
Zapisywanie do bazy danych
Czy próbowałeś już wypełnić i wysłać formularz? Prawdopodobnie zauważyłeś, że ten formularz właściwie nic nie robi. Musimy dołączyć metodę callback, która zapisze wysłane dane.
W linii przed return
w fabryce dla komponentu commentForm
umieścimy następującą linię:
$form->onSuccess[] = $this->commentFormSucceeded(...);
Poprzedni zapis oznacza “po pomyślnym wysłaniu formularza wywołaj metodę commentFormSucceeded
z obecnego
presentera”. Ta metoda jednak jeszcze nie istnieje. Stwórzmy ją więc:
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ękuję za komentarz', 'success');
$this->redirect('this');
}
Tę metodę umieścimy bezpośrednio za fabryką formularza commentForm
.
Ta nowa metoda ma jeden argument, $data
, który jest obiektem zawierającym wysłane dane. Następnie zapisujemy
dane do tabeli bazy danych comments
.
Są tu jeszcze dwie inne metody, które zasługują na wyjaśnienie. Metoda redirect('this')
dosłownie
przekierowuje z powrotem na bieżącą stronę. Jest to wskazane po każdym pomyślnym przetworzeniu formularza. Dzięki temu,
jeśli użytkownik odświeży stronę po wysłaniu formularza, nie zobaczy dobrze znanego komunikatu
Czy chcesz ponownie wysłać dane z formularza?
, który czasami można zobaczyć w przeglądarce. (Ogólnie rzecz
biorąc, po wysłaniu formularza metodą POST
zawsze powinno następować przekierowanie do akcji
GET
.)
Metoda flashMessage()
służy do informowania użytkownika o wyniku jakiejś operacji. Ponieważ przekierowujemy,
wiadomość nie może być po prostu przekazana do szablonu i wyrenderowana. Dlatego istnieje ta metoda, która zapisuje tę
wiadomość i udostępnia ją przy następnym załadowaniu strony. Wiadomości flash są renderowane w głównym szablonie
app/Presentation/@layout.latte
i wygląda to tak:
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
{$flash->message}
</div>
Jak już wiemy, wiadomości flash są automatycznie przekazywane do szablonu, więc nie musimy o tym zbytnio myśleć, po prostu to działa. Aby uzyskać więcej informacji, odwiedź dokumentację.
Renderowanie komentarzy
To jedna z tych rzeczy, które po prostu pokochasz. Nette Database ma świetną funkcję o nazwie Explorer. Pamiętasz jeszcze, że tabele w bazie danych celowo tworzyliśmy za pomocą silnika InnoDB? Adminer stworzył w ten sposób coś, co nazywa się kluczami obcymi, które zaoszczędzą nam mnóstwo pracy.
Nette Database Explorer używa kluczy obcych do rozwiązania wzajemnych relacji między tabelami, a na podstawie znajomości tych relacji potrafi automatycznie tworzyć zapytania do bazy danych.
Jak zapewne pamiętasz, do szablonu przekazaliśmy zmienną $post
za pomocą metody
PostPresenter::renderShow()
i teraz chcemy iterować po wszystkich komentarzach, które mają wartość kolumny
post_id
zgodną z $post->id
. Możemy to osiągnąć, wywołując
$post->related('comments')
. Tak, to takie proste. Spójrzmy na wynikowy kod:
public function renderShow(int $id): void
{
...
$this->template->post = $post;
$this->template->comments = $post->related('comments')->order('created_at');
}
A 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
. Już wiesz, jak działają n:atrybuty
. Jeśli do
atrybutu dodasz prefiks tag-
, funkcjonalność zostanie zastosowana tylko do tagu HTML, a nie do jego zawartości.
Pozwala nam to zrobić z imienia komentatora link tylko w przypadku, gdy podał swój e-mail. Te dwie linie są identyczne:
<strong n:tag-if="$important"> Dzień dobry! </strong>
{if $important}<strong>{/if} Dzień dobry! {if $important}</strong>{/if}