Commenti

Il blog è stato implementato, abbiamo scritto alcuni ottimi post e li abbiamo pubblicati tramite Adminer. Le persone leggono il blog e sono molto appassionate delle nostre idee. Ogni giorno riceviamo molte e-mail di elogio. Ma a cosa servono tutti questi apprezzamenti se li riceviamo solo nell'e-mail, in modo che nessun altro possa leggerli? Non sarebbe meglio se le persone potessero commentare direttamente sul blog, in modo che tutti gli altri possano leggere quanto siamo fantastici?

Rendiamo tutti gli articoli commentabili.

Creare una nuova tabella

Avviare nuovamente Adminer e creare una nuova tabella denominata comments con queste colonne:

  • id int, controllo autoincremento (AI)
  • post_id, una chiave esterna che fa riferimento alla tabella posts
  • name varchar, lunghezza 255
  • email varchar, lunghezza 255
  • content testo
  • created_at timestamp

L'aspetto dovrebbe essere il seguente:

Non dimenticare di utilizzare la memorizzazione delle tabelle InnoDB e premere Salva.

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;

Modulo per i commenti

Per prima cosa, dobbiamo creare un modulo che permetta agli utenti di commentare la nostra pagina. Nette Framework offre un ottimo supporto per i moduli. Possono essere configurati in un presenter e resi in un template.

Nette Framework ha un concetto di componenti. Un componente è una classe o un pezzo di codice riutilizzabile, che può essere collegato a un altro componente. Anche un presentatore è un componente. Ogni componente viene creato utilizzando il component factory. Definiamo quindi il factory del modulo dei commenti in PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // significa Nette\Application\UI\Form

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

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

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

	$form->addSubmit('send', 'Publish comment');

	return $form;
}

Spieghiamolo un po'. La prima riga crea una nuova istanza del componente Form. I metodi seguenti allegano gli input HTML alla definizione del form. ->addText sarà reso come <input type=text name=name>con <label>Your name:</label>. Come si sarà già intuito, ->addTextArea aggiunge un input di tipo <textarea> e ->addSubmit aggiunge un <input type=submit>. Esistono altri metodi del genere, ma questo è tutto ciò che occorre sapere al momento. Per saperne di più, consultare la documentazione.

Una volta definito il componente del modulo in un presentatore, possiamo renderlo (visualizzarlo) in un template. Per farlo, posizionare il tag {control} alla fine del template dei dettagli del post, in Post/show.latte. Poiché il nome del componente è commentForm (deriva dal nome del metodo createComponentCommentForm), il tag avrà il seguente aspetto

...
<h2>Post new comment</h2>

{control commentForm}

Ora, se si controlla il dettaglio di un post, ci sarà un nuovo modulo per inserire i commenti.

Salvataggio nel database

Avete provato a inviare dei dati? Avrete notato che il modulo non esegue alcuna azione. È solo lì, bello da vedere e non fa nulla. Dobbiamo collegare un metodo di callback, che salverà i dati inviati.

Inserire la seguente riga prima della riga return nel factory del componente commentForm:

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

Significa “dopo che il modulo è stato inviato con successo, chiamare il metodo commentFormSucceeded del presentatore corrente”. Questo metodo non esiste ancora, quindi creiamolo.

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('Thank you for your comment', 'success');
	$this->redirect('this');
}

Dovremmo posizionarlo subito dopo il factory del componente commentForm.

Il metodo new ha un parametro, che è l'istanza del form da inviare, creata dal component factory. Riceviamo i valori inviati in $data. Poi inseriamo i dati nella tabella del database comments.

Ci sono altre due chiamate di metodo da spiegare. Il redirect reindirizza letteralmente alla pagina corrente. Si dovrebbe fare ogni volta che il form è stato inviato, è valido e l'operazione di callback ha fatto ciò che doveva fare. Inoltre, quando si reindirizza la pagina dopo l'invio del modulo, non si vedrà il noto messaggio Would you like to submit the post data again?, che a volte si può vedere nel browser. (In generale, dopo aver inviato un modulo con il metodo POST, si dovrebbe sempre reindirizzare l'utente a un'azione GET ).

Il messaggio flashMessage serve a informare l'utente sul risultato di qualche operazione. Poiché si tratta di un rinvio, il messaggio non può essere passato direttamente al template e reso. Quindi c'è questo metodo, che lo memorizza e lo rende disponibile al successivo caricamento della pagina. I messaggi flash sono resi nel file predefinito app/UI/@layout.latte e si presentano così:

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

Come già sappiamo, vengono passati automaticamente al template, quindi non occorre pensarci troppo, funziona e basta. Per maggiori dettagli, consultare la documentazione.

Rendering dei commenti

Questa è una delle cose che vi piacerà di più. Nette Database ha questa bella funzione chiamata Explorer. Ricordate che abbiamo creato le tabelle come InnoDB? Adminer ha creato le cosiddette chiavi esterne che ci risparmieranno un sacco di lavoro.

Nette Database Explorer utilizza le chiavi esterne per risolvere le relazioni tra le tabelle e, conoscendo le relazioni, può creare automaticamente le query per voi.

Come ricorderete, abbiamo passato la variabile $post al modello in PostPresenter::renderShow() e ora vogliamo iterare tutti i commenti che hanno la colonna post_id uguale alla nostra $post->id. Per farlo, basta richiamare $post->related('comments'). È così semplice. Guardate il codice risultante.

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

E il modello:

...
<h2>Comments</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>
...

Si noti l'attributo speciale n:tag-if. Si sa già come funziona n: attributes. Se si antepone l'attributo tag-, questo si avvolgerà solo intorno ai tag, non al loro contenuto. Questo permette di trasformare il nome del commentatore in un link, se ha fornito la sua e-mail. Queste due righe hanno risultati identici:

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

{if $important}<strong>{/if} Hello there! {if $important}</strong>{/if}