Comentarii

Am încărcat blogul pe serverul web și am publicat câteva postări foarte interesante folosind Adminer. Oamenii citesc blogul nostru și sunt foarte entuziasmați de el. Primim zilnic multe e-mailuri cu laude. Dar la ce bun toate aceste laude dacă le avem doar în e-mail și nimeni altcineva nu le poate citi? Ar fi mai bine dacă cititorul ar putea comenta direct articolul, astfel încât toată lumea să poată citi cât de minunați suntem.

Deci, haideți să programăm comentariile.

Crearea unui nou tabel

Pornim Adminer și creăm tabelul comments cu următoarele coloane:

  • id int, bifăm autoincrement (AI)
  • post_id, cheie străină care face referire la tabelul posts
  • name varchar, length 255
  • email varchar, length 255
  • content text
  • created_at timestamp

Tabelul ar trebui să arate cam așa:

Nu uitați să folosiți din nou stocarea 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;

Formular pentru comentarii

În primul rând, trebuie să creăm un formular care să permită utilizatorilor să comenteze postările. Nette Framework are un suport uimitor pentru formulare. Le putem configura în presenter și le putem reda în șablon.

Nette Framework utilizează conceptul de componente. O componentă este o clasă reutilizabilă sau o bucată de cod care poate fi atașată la o altă componentă. Chiar și presenterul este o componentă. Fiecare componentă este creată printr-o fabrică (factory). Vom crea deci o fabrică pentru formularul de comentarii în presenterul PostPresenter.

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

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

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

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

	$form->addSubmit('send', 'Publică comentariul');

	return $form;
}

Să explicăm din nou puțin. Prima linie creează o nouă instanță a componentei Form. Următoarele metode atașează inputuri HTML la definiția acestui formular. ->addText va fi redat ca <input type="text" name="name"> cu <label>Nume:</label>. După cum probabil ghiciți corect, ->addTextArea va fi redat ca <textarea> și ->addSubmit ca <input type="submit">. Există mult mai multe metode similare, dar acestea sunt suficiente pentru acest formular deocamdată. Puteți citiți mai multe în documentație.

Dacă formularul este deja definit în presenter, îl putem reda (afișa) în șablon. Facem acest lucru plasând tag-ul {control} la sfârșitul șablonului care redă o postare specifică, în Post/show.latte. Deoarece componenta se numește commentForm (numele este derivat din numele metodei createComponentCommentForm), tag-ul va arăta astfel:

...
<h2>Adăugați un comentariu nou</h2>

{control commentForm}

Acum, dacă vizualizați pagina cu detaliile postării, veți vedea noul formular de comentarii la sfârșitul acesteia.

Salvarea în baza de date

Ați încercat deja să completați și să trimiteți formularul? Probabil ați observat că formularul nu face de fapt nimic. Trebuie să atașăm o metodă callback care va salva datele trimise.

Pe linia dinaintea return în fabrica pentru componenta commentForm, plasăm următoarea linie:

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

Notația anterioară înseamnă “după trimiterea cu succes a formularului, apelează metoda commentFormSucceeded din presenterul curent”. Această metodă însă nu există încă. Haideți să o creăm:

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('Vă mulțumim pentru comentariu', 'success');
	$this->redirect('this');
}

Plasăm această metodă imediat după fabrica formularului commentForm.

Această nouă metodă are un argument, care este instanța formularului care a fost trimis – creat de fabrică. Obținem valorile trimise în $data. Și apoi salvăm datele în tabelul comments din baza de date.

Mai sunt încă două metode care merită explicate. Metoda redirect redirecționează literalmente înapoi la pagina curentă. Este indicat să faceți acest lucru după fiecare trimitere a formularului, dacă acesta conținea date valide și callback-ul a efectuat operația așa cum trebuia. De asemenea, dacă redirecționăm pagina după trimiterea formularului, nu vom vedea mesajul binecunoscut Doriți să retrimiteți datele formularului?, pe care îl putem vedea uneori în browser. (În general, după trimiterea unui formular prin metoda POST, ar trebui să urmeze întotdeauna o redirecționare către o acțiune GET.)

Metoda flashMessage este pentru informarea utilizatorului despre rezultatul unei operații. Deoarece redirecționăm, mesajul nu poate fi pur și simplu transmis șablonului și redat. De aceea există această metodă, care salvează acest mesaj și îl face disponibil la următoarea încărcare a paginii. Mesajele flash sunt redate în șablonul principal app/Presentation/@layout.latte și arată astfel:

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

După cum știm deja, mesajele flash sunt transmise automat șablonului, așa că nu trebuie să ne gândim prea mult la asta, pur și simplu funcționează. Pentru mai multe informații, vizitați documentația.

Redarea comentariilor

Acesta este unul dintre acele lucruri pe care pur și simplu le veți iubi. Nette Database are o funcție grozavă numită Explorer. Vă mai amintiți că am creat intenționat tabelele din baza de date folosind stocarea InnoDB? Adminer a creat astfel ceva numit chei străine, care ne vor economisi multă muncă.

Nette Database Explorer folosește cheile străine pentru a rezolva relația reciprocă dintre tabele și, cunoscând aceste relații, poate crea automat interogări în baza de date.

După cum vă amintiți cu siguranță, am transmis variabila $post șablonului folosind metoda PostPresenter::renderShow() și acum dorim să iterăm prin toate comentariile care au valoarea coloanei post_id identică cu $post->id. Putem realiza acest lucru apelând $post->related('comments'). Da, atât de simplu. Să vedem codul rezultat:

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

Și șablonul:

...
<h2>Comentarii</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 scris:</p>

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

Observați atributul special n:tag-if. Știți deja cum funcționează n:atributele. Dacă adăugați prefixul tag- la atribut, funcționalitatea se aplică doar tag-ului HTML, nu și conținutului său. Acest lucru ne permite să transformăm numele comentatorului într-un link doar dacă și-a furnizat e-mailul. Aceste două linii sunt identice:

<strong n:tag-if="$important"> Bună ziua! </strong>

{if $important}<strong>{/if} Bună ziua! {if $important}</strong>{/if}