Commentaires

Le blog est déployé, nous avons écrit quelques articles très intéressants et les avons publiés à l'aide d'Adminer. Les gens lisent notre blog et s'en enthousiasment. Nous recevons chaque jour de nombreux e-mails élogieux. Mais à quoi bon tous ces éloges s'ils restent dans notre boîte mail et que personne d'autre ne peut les lire ? Ne serait-il pas préférable que les lecteurs puissent commenter directement les articles, pour que tout le monde voie à quel point nous sommes formidables ?

Implémentons les commentaires.

Créer une nouvelle table

Relançons Adminer et créons une table comments avec ces colonnes :

  • id int, cochez autoincrement (AI)
  • post_id, clé étrangère référençant la table posts
  • name varchar, longueur 255
  • email varchar, longueur 255
  • content text
  • created_at timestamp

La table devrait ressembler à ceci :

N'oubliez pas d'utiliser de nouveau le moteur de stockage 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;

Formulaire de commentaire

Nous devons d'abord créer un formulaire permettant aux utilisateurs de commenter les articles. Nette Framework prend excellemment en charge les formulaires. Nous pouvons les configurer dans le presenter et les rendre dans le template.

Nette Framework utilise la notion de composants. Un composant est une classe ou un morceau de code réutilisable qui peut être rattaché à un autre composant. Même un presenter est un composant. Chaque composant est créé par une méthode fabrique. Créons dans PostPresenter une factory pour le formulaire de commentaire.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // c'est-à-dire Nette\Application\UI\Form

	$form->addText('name', 'Votre nom :')
		->setRequired();

	$form->addEmail('email', 'E-mail :');

	$form->addTextArea('content', 'Commentaire :')
		->setRequired();

	$form->addSubmit('send', 'Publier le commentaire');

	return $form;
}

Expliquons cela brièvement. La première ligne crée une nouvelle instance du composant Form. Les méthodes suivantes rattachent des champs HTML à la définition du formulaire. ->addText se rend en <input type="text" name="name"> avec un <label>Votre nom :</label>. Comme vous vous en doutez, ->addTextArea rattache un <textarea> et ->addSubmit ajoute un <input type="submit">. Il existe beaucoup de méthodes semblables, mais celles-ci suffisent pour l'instant. Vous pouvez en apprendre davantage dans la documentation.

Une fois le composant formulaire défini dans le presenter, nous pouvons le rendre (l'afficher) dans le template. Pour cela, placez la balise {control} à la fin du template qui affiche un article isolé, dans Post/show.latte. Comme le composant s'appelle commentForm (dérivé du nom de la méthode createComponentCommentForm), la balise ressemblera à ceci :

...
<h2>Publier un nouveau commentaire</h2>

{control commentForm}

Désormais, si vous consultez la page de détail d'un article, vous verrez le nouveau formulaire de commentaire à la fin.

Enregistrer dans la base de données

Avez-vous essayé de remplir et d'envoyer le formulaire ? Vous avez peut-être remarqué qu'il ne fait encore rien. Nous devons y rattacher une méthode de rappel qui enregistrera les données soumises.

Ajoutez la ligne suivante avant l'instruction return de la méthode fabrique du composant commentForm :

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

Cette ligne signifie “après l'envoi réussi du formulaire, appelle la méthode commentFormSucceeded du presenter courant”. Cette méthode n'existe pas encore, créons-la donc :

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('Merci pour votre commentaire', 'success');
	$this->redirect('this');
}

Vous devriez la placer juste après la factory du composant commentForm.

La nouvelle méthode a un argument, qui est l'instance du formulaire envoyé, créée par la factory du composant. Nous recevons les valeurs soumises dans $data. Puis nous insérons les données dans la table comments de la base.

Il reste deux appels de méthode à expliquer. La méthode redirect('this') redirige littéralement vers la page courante. Vous devriez le faire chaque fois qu'un formulaire est envoyé avec succès et que l'opération de rappel s'est terminée. Rediriger après l'envoi d'un formulaire évite aussi le fameux message “Voulez-vous renvoyer les données du formulaire ?” du navigateur. (De façon générale, après l'envoi d'un formulaire par la méthode POST, vous devriez toujours rediriger vers une action GET.)

La méthode flashMessage sert à informer l'utilisateur du résultat d'une opération. Comme nous redirigeons, le message ne peut pas être simplement passé au template et rendu directement. Cette méthode enregistre le message et le rend disponible au chargement suivant de la page. Les messages flash sont rendus dans le template de layout principal app/Presentation/@layout.latte, ainsi :

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

Comme nous le savons déjà, les messages flash sont automatiquement passés au template, nous n'avons donc pas à y réfléchir : cela fonctionne tout seul. Pour plus de détails, consultez la documentation.

Rendre les commentaires

C'est l'une de ces fonctionnalités que vous allez tout simplement adorer. Nette Database possède une excellente fonctionnalité appelée Explorer. Vous vous souvenez que nous avons créé exprès les tables avec le moteur InnoDB ? Adminer a créé ce qu'on appelle des clés étrangères, qui nous épargnent beaucoup de travail.

Nette Database Explorer utilise ces clés étrangères pour résoudre les relations entre les tables. Connaissant ces relations, il peut créer automatiquement les requêtes de base de données pour vous.

Comme vous vous en souvenez peut-être, nous avons passé la variable $post au template dans PostPresenter::renderShow(). Nous voulons maintenant parcourir tous les commentaires dont la colonne post_id correspond à notre $post->id. Nous pouvons y parvenir en appelant $post->related('comments'). Oui, c'est aussi simple que cela. Regardons le code final :

public function renderShow(int $id): void
{
	// ...
	$this->template->post = $post;
	$this->template->comments = $post->related('comments')->order('created_at');
}

Et le template :

...
<h2>Commentaires</h2>

<div class="comments">
	{foreach $comments as $comment}
		<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
			{$comment->name}
		</a></b> a dit :</p>

		<div>{$comment->content}</div>
	{/foreach}
</div>
...

Remarquez l'attribut particulier n:tag-if. Vous savez déjà comment fonctionnent les n:attributs. Si vous préfixez l'attribut par tag-, la fonctionnalité ne s'applique qu'à la balise HTML elle-même, pas à son contenu. Cela nous permet de ne faire du nom du commentateur un lien que s'il a fourni son e-mail. Ces deux lignes produisent des résultats identiques :

<strong n:tag-if="$important"> Bonjour ! </strong>

{if $important}<strong>{/if} Bonjour ! {if $important}</strong>{/if}