Kommentare
Der Blog ist online, wir haben ein paar sehr interessante Blogbeiträge geschrieben und sie mit Adminer veröffentlicht. Die Leute lesen unseren Blog und sind ganz begeistert davon. Wir bekommen täglich viele E-Mails mit Lob. Aber was nützt all dieses Lob, wenn es nur in unserer E-Mail steht und es niemand sonst lesen kann? Wäre es nicht besser, wenn die Leser die Artikel direkt kommentieren könnten, sodass alle sehen, wie großartig wir sind?
Setzen wir die Kommentare um.
Eine neue Tabelle anlegen
Starten wir Adminer wieder und legen wir eine Tabelle comments mit diesen Spalten an:
idint, autoincrement (AI) ankreuzenpost_id, Fremdschlüssel, der auf die Tabellepostsverweistnamevarchar, Länge 255emailvarchar, Länge 255contenttextcreated_attimestamp
Die Tabelle sollte ungefähr so aussehen:

Denken Sie daran, wieder die Storage Engine InnoDB zu verwenden.
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;
Formular zum Kommentieren
Zuerst müssen wir ein Formular erstellen, das es Benutzern erlaubt, Beiträge zu kommentieren. Das Nette Framework hat eine hervorragende Unterstützung für Formulare. Wir können sie im Presenter konfigurieren und im Template rendern.
Das Nette Framework verwendet das Konzept der Komponenten. Eine Komponente ist eine wiederverwendbare Klasse oder
ein Stück Code, das sich an eine andere Komponente anhängen lässt. Auch ein Presenter ist eine Komponente. Jede Komponente wird
über eine Factory-Methode erzeugt. Erstellen wir im PostPresenter eine Factory für das Kommentarformular.
protected function createComponentCommentForm(): Form
{
$form = new Form; // bedeutet Nette\Application\UI\Form
$form->addText('name', 'Ihr Name:')
->setRequired();
$form->addEmail('email', 'E-Mail:');
$form->addTextArea('content', 'Kommentar:')
->setRequired();
$form->addSubmit('send', 'Kommentar veröffentlichen');
return $form;
}
Erklären wir das kurz. Die erste Zeile erzeugt eine neue Instanz der Komponente Form. Die folgenden Methoden
hängen HTML-Eingabefelder an die Definition des Formulars an. ->addText rendert als
<input type="text" name="name"> mit einem <label>Ihr Name:</label>. Wie Sie sich
denken können, hängt ->addTextArea ein <textarea> an, und ->addSubmit fügt
ein <input type="submit"> hinzu. Es gibt viele ähnliche Methoden, aber diese genügen vorerst. Mehr erfahren Sie in der Dokumentation.
Sobald die Formularkomponente im Presenter definiert ist, können wir sie im Template rendern (anzeigen). Setzen Sie dazu den
Tag {control} ans Ende des Templates, das einen einzelnen Beitrag anzeigt, also in Post/show.latte. Da
die Komponente commentForm heißt (abgeleitet vom Methodennamen createComponentCommentForm), sieht der
Tag so aus:
...
<h2>Neuen Kommentar schreiben</h2>
{control commentForm}
Wenn Sie nun die Detailseite eines Beitrags ansehen, sehen Sie am Ende das neue Kommentarformular.
In der Datenbank speichern
Haben Sie versucht, das Formular auszufüllen und abzuschicken? Dann ist Ihnen vielleicht aufgefallen, dass das Formular noch gar nichts tut. Wir müssen eine Callback-Methode anhängen, die die gesendeten Daten speichert.
Fügen Sie in der Factory-Methode für die Komponente commentForm vor der Anweisung return die
folgende Zeile ein:
$form->onSuccess[] = $this->commentFormSucceeded(...);
Diese Zeile bedeutet “rufe nach dem erfolgreichen Absenden des Formulars die Methode commentFormSucceeded des
aktuellen Presenters auf”. Diese Methode existiert noch nicht, erstellen wir sie also:
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('Danke für Ihren Kommentar', 'success');
$this->redirect('this');
}
Sie sollten sie direkt hinter die Factory der Komponente commentForm setzen.
Die neue Methode hat ein Argument, nämlich die Instanz des abgeschickten Formulars, die von der Komponenten-Factory erzeugt
wurde. Die gesendeten Werte bekommen wir in $data. Und dann fügen wir die Daten in die Datenbanktabelle
comments ein.
Zwei weitere Methodenaufrufe sind zu erklären. Die Methode redirect('this') leitet buchstäblich zurück auf die
aktuelle Seite. Das sollten Sie jedes Mal tun, wenn ein Formular erfolgreich abgeschickt wurde und die Operation im Callback
abgeschlossen ist. Die Weiterleitung nach dem Absenden des Formulars verhindert außerdem die bekannte Meldung “Möchten Sie die
Formulardaten erneut senden?” im Browser. (Allgemein sollten Sie nach dem Absenden eines Formulars mit der Methode
POST immer auf eine GET-Aktion weiterleiten.)
Die Methode flashMessage dient dazu, den Benutzer über das Ergebnis einer Operation zu informieren. Da wir
weiterleiten, lässt sich die Meldung nicht einfach an das Template übergeben und direkt rendern. Diese Methode speichert die
Meldung und stellt sie beim nächsten Laden der Seite bereit. Flash-Meldungen werden im Haupt-Layout-Template
app/Presentation/@layout.latte gerendert, und zwar so:
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
{$flash->message}
</div>
Wie wir bereits wissen, werden Flash-Meldungen automatisch an das Template übergeben, wir müssen also nicht groß darüber nachdenken; es funktioniert einfach. Mehr Details finden Sie in der Dokumentation.
Kommentare rendern
Das ist eine dieser Funktionen, die Sie schlicht lieben werden. Nette Database hat eine großartige Funktion namens Explorer. Erinnern Sie sich, dass wir die Datenbanktabellen absichtlich mit der Engine InnoDB angelegt haben? Adminer hat dabei sogenannte Fremdschlüssel erzeugt, die uns eine Menge Arbeit sparen.
Nette Database Explorer nutzt diese Fremdschlüssel, um die Beziehungen zwischen den Tabellen aufzulösen. Da es diese Beziehungen kennt, kann es die Datenbankabfragen automatisch für Sie erzeugen.
Wie Sie sich erinnern, haben wir in PostPresenter::renderShow() die Variable $post an das Template
übergeben. Nun wollen wir über alle Kommentare iterieren, bei denen die Spalte post_id zu unserem
$post->id passt. Das erreichen wir mit dem Aufruf $post->related('comments'). Ja, so einfach ist
das. Sehen wir uns den endgültigen Code an:
public function renderShow(int $id): void
{
// ...
$this->template->post = $post;
$this->template->comments = $post->related('comments')->order('created_at');
}
Und das Template:
...
<h2>Kommentare</h2>
<div class="comments">
{foreach $comments as $comment}
<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
{$comment->name}
</a></b> schrieb:</p>
<div>{$comment->content}</div>
{/foreach}
</div>
...
Beachten Sie das besondere Attribut n:tag-if. Wie n:Attribute funktionieren, wissen Sie bereits.
Stellen Sie dem Attribut tag- voran, gilt die Funktion nur für den HTML-Tag selbst, nicht für seinen Inhalt.
Dadurch können wir den Namen des Kommentierenden nur dann zu einem Link machen, wenn er seine E-Mail-Adresse angegeben hat. Diese
beiden Zeilen liefern dasselbe Ergebnis:
<strong n:tag-if="$important"> Hello there! </strong>
{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if}